《Windows PE》7.4 资源表应用

本节我们将通过两个示例程序,演示对PE文件内图标资源的置换与提取。

本节必须掌握的知识点:

        更改图标

        提取图标资源

7.4.1 更改图标

让我们来做一个实验,替换PE文件中现有的图标。如果手工替换,一定是先找到资源表,然后分别替换图标资源和图标组资源就可以了。当然这个过程稍微有点繁复,可能会涉及到其他资源的重定位。还好,Windows操作系统为开发者提供了一组更新PE文件中资源的API函数:BeginUpdateResource、UpdateResource、EndUpdateResource。用来枚举 PE 文件中资源的函数有:EnumResourceTypes、EnumResourceNames、EnumResourceLanguages。具体的使用方法可以参见MSDN,下面我们将使用这些函数实现图标资源的替换。

实验五十:更改图标

●模块1:resource.h(略)

●模块2:peinfo.rc(略)

●模块3:info.h

#pragma once
#ifndef INFO_H_
#define INFO_H_#include <windows.h>
#include <richedit.h>	//CHARFORMAT富文本结构定义
#include <commctrl.h>	//通用控件
#pragma comment(lib,"comctl32.lib")
#include <strsafe.h>	//StringCchCopy
#include <stdlib.h>// 文件中的ICO头部
typedef struct
{byte bWidth;				//宽度byte bHeight;				//高度byte bColorCount;			//颜色数byte bReserved;			//保留字,必须为0WORD wPlanes;			//调色板WORD wBitCount;			//每个像素的位数DWORD dwBytesInRes;		//资源长度DWORD dwImageOffset;	//资源在文件偏移
}ICON_DIR_ENTRY;typedef struct
{WORD idReserved;			//保留字,必须为0WORD idType;				//资源类别,如果是1表示为ICO文件WORD idCount;				//图标数量//ICON_DIR_ENTRY idEntries;	//图标项,一个图标一项
}ICON_DIR;//PE中ICO头部
typedef struct
{byte bWidth;			//宽度byte bHeight;			//高度byte bColorCount;		//颜色数byte bReserved;		//保留字,必须为0WORD wPlanes;		//调色板WORD wBitCount;		//每个像素的位数DWORD dwBytesInRes;	//资源长度WORD nID;			//资源在文件序号
}PE_ICON_DIR_ENTRY;
typedef struct
{WORD idReserved;	//保留字,必须为0WORD idType;		//资源类别,如果是1表示为ICO文件WORD idCount;		//图标数量PE_ICON_DIR_ENTRY idEntries;	//图标项,一个图标一项
}PE_ICON_DIR;//函数声明
BOOL CALLBACK DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void Exception(void);
void init(); //初始化
void  _OpenFile();//打开PE文件并处理
int  CALLBACK _Handler(EXCEPTION_POINTERS * lpExceptionPoint);
void ShowErrMsg();
void _AppendInfo(const TCHAR * _lpsz);//往文本框中追加文本
//将boy.ico图标替换指定PE程序的图标
BOOL _doUpdate(TCHAR* lpszFile, TCHAR* lpszExeFile);#endif

●模块4:PEUpdateIcon.c

/*------------------------------------------------------------------------FileName: PEUpdateIcon.c实验50:更改程序图标实例(支持32位和64位PE文件)(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <strsafe.h>	//StringCchCopy
#include "resource.h"
#include "info.h"HANDLE hInstance;
HWND hWinMain, hWinEdit;
HMODULE hRichEdit;
TCHAR szFileName[MAX_PATH];
HANDLE hFileDump;
HANDLE hFile;int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nCmdShow)
{TCHAR	szDllEdit[] = TEXT("RichEd20.dll");TCHAR	szClassEdit[] = TEXT("RichEdit20W");//peinfo.rc中定义hRichEdit = LoadLibrary((LPCWSTR)&szDllEdit);hInstance = GetModuleHandle(NULL);DialogBoxParam(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, 
(DLGPROC)DlgProc, (LPARAM)0);FreeLibrary(hRichEdit);return 0;
}//初始化窗口函数
void init()
{CHARFORMAT stCf;TCHAR	szClassEdit[] = TEXT("RichEdit20A");TCHAR	szFont[] = TEXT("宋体");HINSTANCE hInstance;hWinEdit = GetDlgItem(hWinMain, IDC_INFO);//为窗口程序设置图标hInstance = GetModuleHandle(NULL);HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(ICO_MAIN));//HICON hIcon = LoadIcon(hInstance, TEXT("#111"));SendMessage(hWinMain, WM_SETICON, ICON_BIG, (LPARAM)hIcon);//设置编辑控件SendMessage(hWinEdit, EM_SETTEXTMODE, TM_PLAINTEXT, 0);RtlZeroMemory(&stCf, sizeof(stCf));stCf.cbSize = sizeof(stCf);stCf.yHeight = 9 * 20;stCf.dwMask = CFM_FACE | CFM_SIZE | CFM_BOLD;StringCchCopy((LPTSTR)&stCf.szFaceName, lstrlen(szFont) + 1, (LPCTSTR)&szFont);SendMessage(hWinEdit, EM_SETCHARFORMAT, 0, (LPARAM)&stCf);SendMessage(hWinEdit, EM_EXLIMITTEXT, 0, -1);//设为-1表示无限制
}//富文本窗口回调函数
BOOL CALLBACK DlgProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{const TCHAR szErr[] = TEXT("文件格式错误!");const TCHAR szErrFormat[] = TEXT("这个文件不是PE格式的文件!");switch (wMsg){case WM_CLOSE:EndDialog(hWnd, 0);return TRUE;case WM_INITDIALOG:hWinMain = hWnd;init();	//初始化return TRUE;case WM_COMMAND:switch (wParam){case IDM_EXIT:EndDialog(hWnd, 0);return TRUE;case IDM_OPEN:_OpenFile();return TRUE;case IDM_1:MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);return TRUE;case IDM_2:MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);return TRUE;case IDM_3:MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);return TRUE;}}return FALSE;
}//执行比对PE文件
void _OpenFile()
{OPENFILENAME stOF;HANDLE hFile = NULL;HANDLE hMapFile = NULL;PBYTE lpMemory = NULL;	//PE文件内存映射文件地址int dwFileSize;const TCHAR szDefaultExt[] = TEXT("exe");const TCHAR szFilter[] = TEXT("PE Files (*.exe)\0*.exe\0")\TEXT("DLL Files(*.dll)\0*.dll\0")\TEXT("SCR Files(*.scr)\0*.scr\0")\TEXT("FON Files(*.fon)\0*.fon\0")\TEXT("DRV Files(*.drv)\0*.drv\0")\TEXT("All Files(*.*)\0*.*\0\0");const TCHAR szErr[] = TEXT("文件格式错误!");const TCHAR szErrFormat[] = TEXT("操作文件时出现错误!");const TCHAR szOut1[] = TEXT("----------------------------------------------------------------\r\n")TEXT("待处理的PE文件:%s\r\n");static TCHAR lpszBoyIcon[] = 
TEXT("D:\\code\\winpe\\ch07\\PEUpdateIcon\\boy.ico");const TCHAR szFailure[] = TEXT("执行图标修改失败。");const TCHAR szSuccess[] = TEXT("恭喜你,图标修改成功成功的。");IMAGE_DOS_HEADER *lpstDOS;	//DOS块地址IMAGE_NT_HEADERS *lpstNT;	//PE文件头地址//显示打开文件对话框RtlZeroMemory(&stOF, sizeof(stOF));stOF.lStructSize = sizeof(stOF);stOF.hwndOwner = hWinMain;stOF.lpstrFilter = szFilter;stOF.lpstrFile = szFileName;stOF.nMaxFile = MAX_PATH;stOF.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;stOF.lpstrDefExt = szDefaultExt;if (!GetOpenFileName(&stOF))return;//打开PE文件hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ | 
FILE_SHARE_WRITE, NULL,OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);if (hFile == INVALID_HANDLE_VALUE)MessageBox(NULL, TEXT("打开文件失败!"), NULL, MB_ICONWARNING);else{//获取文件大小dwFileSize = GetFileSize(hFile, NULL);//创建内存映射文件if (dwFileSize){if (hMapFile = CreateFileMapping(hFile, 
NULL, PAGE_READONLY, 0, 0, NULL)){//获得文件在内存的映象起始位置lpMemory = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);//异常处理方法2:if (!lpMemory){atexit(Exception);goto _ErrFormat;exit(EXIT_FAILURE);}//检查PE文件是否有效lpstDOS = (IMAGE_DOS_HEADER *)lpMemory;if (lpstDOS->e_magic != IMAGE_DOS_SIGNATURE){//如果非PE文件-异常处理atexit(Exception);goto _ErrFormat;exit(EXIT_FAILURE);}lpstNT = (IMAGE_NT_HEADERS *)(lpMemory + lpstDOS->e_lfanew);if (lpstNT->Signature != IMAGE_NT_SIGNATURE){//如果非PE文件-异常处理atexit(Exception);goto _ErrFormat;exit(EXIT_FAILURE);}//将boy.ico的图标数据写入PE文件if (_doUpdate(lpszBoyIcon, szFileName))_AppendInfo(szSuccess);else_AppendInfo(szFailure);goto _ErrorExit;_ErrFormat:MessageBox(hWinMain, szErrFormat, NULL, MB_OK);_ErrorExit:if (lpMemory)UnmapViewOfFile(lpMemory);}if (hMapFile)CloseHandle(hMapFile);}if (hFile)CloseHandle(hFile);}return;
}//RITCH控件添加文本信息--以 null 结尾的字符串
void _AppendInfo(const TCHAR * _lpsz)
{CHARRANGE stCR;//检索文本控件内文本的长度stCR.cpMin = GetWindowTextLength(hWinEdit);stCR.cpMax = stCR.cpMin;//择并替换文本控件的选定内容SendMessage(hWinEdit, EM_EXSETSEL, 0, (LPARAM)&stCR);//EM_REPLACESEL以 null 结尾的字符串的指针替换SendMessage(hWinEdit, EM_REPLACESEL, FALSE, (LPARAM)_lpsz);
}//异常处理
void Exception(void)
{MessageBox(hWinMain, TEXT("获得文件在内存的映象起始位置失败!"), NULL, MB_OK);
}/*
;-----------------------------------
;将boy.ico图标替换指定PE程序的图标
;使用win32 api函数UpdateResource实现此功能
;-----------------------------------
*/
BOOL _doUpdate(TCHAR* lpszFile, TCHAR* lpszExeFile)
{ICON_DIR stID;ICON_DIR_ENTRY stIDE;PE_ICON_DIR stGID;HANDLE hFile, hUpdate;DWORD dwReserved, nSize, nGSize;BOOL flag = FALSE;LPVOID pIcon, pGrpIcon;hFile = CreateFile(lpszFile,GENERIC_READ,0,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,NULL);if (hFile == INVALID_HANDLE_VALUE)return 0;RtlZeroMemory(&stID,sizeof(stID));ReadFile(hFile,&stID,sizeof(stID),&dwReserved,NULL);RtlZeroMemory(&stIDE, sizeof(stIDE));ReadFile(hFile, &stIDE, sizeof(stIDE), &dwReserved, NULL);nSize = stIDE.dwBytesInRes;pIcon = GlobalAlloc(GPTR, nSize);SetFilePointer(hFile, stIDE.dwImageOffset, NULL, FILE_BEGIN);if (!ReadFile(hFile, pIcon, nSize, &dwReserved, NULL))goto _ret;RtlZeroMemory(&stGID,sizeof(stGID));stGID.idCount = stID.idCount;stGID.idReserved = 0;stGID.idType = 1;RtlMoveMemory(&stGID.idEntries,&stIDE,12);stGID.idEntries.nID = 0;nGSize = sizeof(stGID);pGrpIcon = GlobalAlloc(GPTR, nGSize);RtlMoveMemory(pGrpIcon,&stGID,nGSize);//开始修改hUpdate = BeginUpdateResource(lpszExeFile,FALSE); //检索句柄,可使用该句柄在二进制模块中添加,删除或替换资源UpdateResource(hUpdate,RT_GROUP_ICON,(LPCWSTR)1,LANG_CHINESE,pGrpIcon,nGSize);flag = UpdateResource(hUpdate,RT_ICON, (LPCWSTR)1,LANG_CHINESE,pIcon,nSize);EndUpdateResource(hUpdate,FALSE);if (flag == FALSE)MessageBox(NULL, L"替换资源失败!", NULL, MB_OK);elsegoto over;
_ret:GlobalFree(pIcon);CloseHandle(hFile);return flag;
over:CloseHandle(hFile);return flag;
}

运行:

图7-7 替换图标

 

总结

       上述示例选择打开一个PE文件,然后_doUpdate函数实现图标资源的替换。具体实现步骤如下:

1.首先调用CreateFile函数打开boy.ico图标文件。

2.然后调用ReadFile函数,分别将图标文件的头部信息和图标项读取到缓冲区中。

3.接着分配一个和图标资源大小一样的内存空间,并将整个图标文件读取到该内存中。

4.然后再将初始化后的PE_ICON_DIR结构变量stGID复制到一个新分配的内存块中。

5.最后调用一组资源替换的API函数BeginUpdateResource、UpdateResource和EndUpdateResource完成替换。

7.4.2 提取图标资源

一个ICO文件由三大部分组成:第一部分是图标头,第二部分是图标项,第三部分为图标数据。PE资源表中,第一部分和第二部分组合成图标组资源,第三部分的每个图标数据对应一个图标资源。接下来我们给出一个提取图标的示例程序。

实验五十一:提取图标资源

●模块1:resource.h(略)

●模块2:peinfo.rc(略)

●模块3:info.h

#pragma once
#ifndef INFO_H_
#define INFO_H_#include <windows.h>
#include <richedit.h>	//CHARFORMAT富文本结构定义
#include <commctrl.h>	//通用控件
#pragma comment(lib,"comctl32.lib")
#include <strsafe.h>	//StringCchCopy
#include <stdlib.h>//函数声明
BOOL CALLBACK DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
//void Exception(void);
void init(); //初始化
void  _OpenFile();//打开PE文件并处理
DWORD RVAToOffset(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA);// 将内存偏移量RVA转换为文件偏移
DWORD GetRVASection(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA);//查找 RVA 所在的节区
int  CALLBACK _Handler(EXCEPTION_POINTERS * lpExceptionPoint);
//void ShowErrMsg();
void _AppendInfo(const TCHAR * _lpsz);//往文本框中追加文本
//PE文件处理模块
void _getResource(PBYTE, IMAGE_NT_HEADERS *, int);//获取PE文件的资源信息
void ProcessRes(PBYTE lpFile, PBYTE lpRes, IMAGE_RESOURCE_DIRECTORY * lpResDir, DWORD dwLevel);
void _getIcoData(PBYTE lpFile, PBYTE lpRes, DWORD number, DWORD off, DWORD size);//处理单个ICO文件
int _getFinnalData(PBYTE lpFile, PBYTE lpRes, DWORD number);//将图标数据写入文件
#endif

●模块4:RvaToFileOffset.c(略)

●模块5:PEDumpIcon.c

/*------------------------------------------------------------------------FileName:PEDumpIcon.c实验51:提取程序图标实例(支持32位和64位PE文件)(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <strsafe.h>	//StringCchCopy
#include "resource.h"
#include "info.h"HANDLE hInstance;
HWND hWinMain, hWinEdit;
HMODULE hRichEdit;
TCHAR szFileName[MAX_PATH];
HANDLE hFileDump;
HANDLE hFile;int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nCmdShow)
{TCHAR	szDllEdit[] = TEXT("RichEd20.dll");TCHAR	szClassEdit[] = TEXT("RichEdit20W");//peinfo.rc中定义hRichEdit = LoadLibrary((LPCWSTR)&szDllEdit);hInstance = GetModuleHandle(NULL);DialogBoxParam(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DlgProc, (LPARAM)0);FreeLibrary(hRichEdit);return 0;
}//初始化窗口函数
void init()
{CHARFORMAT stCf;TCHAR	szClassEdit[] = TEXT("RichEdit20A");TCHAR	szFont[] = TEXT("宋体");HINSTANCE hInstance;hWinEdit = GetDlgItem(hWinMain, IDC_INFO);//为窗口程序设置图标hInstance = GetModuleHandle(NULL);HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(ICO_MAIN));//HICON hIcon = LoadIcon(hInstance, TEXT("#111"));SendMessage(hWinMain, WM_SETICON, ICON_BIG, (LPARAM)hIcon);//设置编辑控件SendMessage(hWinEdit, EM_SETTEXTMODE, TM_PLAINTEXT, 0);RtlZeroMemory(&stCf, sizeof(stCf));stCf.cbSize = sizeof(stCf);stCf.yHeight = 9 * 20;stCf.dwMask = CFM_FACE | CFM_SIZE | CFM_BOLD;StringCchCopy((LPTSTR)&stCf.szFaceName, lstrlen(szFont) + 1, (LPCTSTR)&szFont);SendMessage(hWinEdit, EM_SETCHARFORMAT, 0, (LPARAM)&stCf);SendMessage(hWinEdit, EM_EXLIMITTEXT, 0, -1);//设为-1表示无限制
}//富文本窗口回调函数
BOOL CALLBACK DlgProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{const TCHAR szErr[] = TEXT("文件格式错误!");const TCHAR szErrFormat[] = TEXT("这个文件不是PE格式的文件!");switch (wMsg){case WM_CLOSE:EndDialog(hWnd, 0);return TRUE;case WM_INITDIALOG:hWinMain = hWnd;init();	//初始化return TRUE;case WM_COMMAND:switch (wParam){case IDM_EXIT:EndDialog(hWnd, 0);return TRUE;case IDM_OPEN:_OpenFile();return TRUE;case IDM_1:MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);return TRUE;case IDM_2:MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);return TRUE;case IDM_3:MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);return TRUE;}}return FALSE;
}//执行比对PE文件
void _OpenFile()
{OPENFILENAME stOF;HANDLE hFile = NULL;HANDLE hMapFile = NULL;PBYTE lpMemory = NULL;	//PE文件内存映射文件地址int dwFileSize;const TCHAR szDefaultExt[] = TEXT("exe");const TCHAR szFilter[] = TEXT("PE Files (*.exe)\0*.exe\0")\TEXT("DLL Files(*.dll)\0*.dll\0")\TEXT("SCR Files(*.scr)\0*.scr\0")\TEXT("FON Files(*.fon)\0*.fon\0")\TEXT("DRV Files(*.drv)\0*.drv\0")\TEXT("All Files(*.*)\0*.*\0\0");const TCHAR szErr[] = TEXT("文件格式错误!");const TCHAR szErrFormat[] = TEXT("操作文件时出现错误!");const TCHAR szOut1[] = TEXT("----------------------------------------------------------------\r\n")TEXT("待处理的PE文件:%s\r\n");static TCHAR szBuffer[256];IMAGE_DOS_HEADER *lpstDOS;	//DOS块地址IMAGE_NT_HEADERS *lpstNT;	//PE文件头地址//显示打开文件对话框RtlZeroMemory(&stOF, sizeof(stOF));stOF.lStructSize = sizeof(stOF);stOF.hwndOwner = hWinMain;stOF.lpstrFilter = szFilter;stOF.lpstrFile = szFileName;stOF.nMaxFile = MAX_PATH;stOF.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;stOF.lpstrDefExt = szDefaultExt;if (!GetOpenFileName(&stOF))return;//打开PE文件hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);if (hFile == INVALID_HANDLE_VALUE)MessageBox(NULL, TEXT("打开文件失败!"), NULL, MB_ICONWARNING);else{//获取文件大小dwFileSize = GetFileSize(hFile, NULL);//创建内存映射文件if (dwFileSize){if (hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL)){//获得文件在内存的映象起始位置lpMemory = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);//异常处理方法2:if (!lpMemory){MessageBox(hWinMain, szErrFormat, NULL, MB_OK);}//检查PE文件是否有效lpstDOS = (IMAGE_DOS_HEADER *)lpMemory;if (lpstDOS->e_magic != IMAGE_DOS_SIGNATURE){//如果非PE文件-异常处理MessageBox(hWinMain, szErrFormat, NULL, MB_OK);}lpstNT = (IMAGE_NT_HEADERS *)(lpMemory + lpstDOS->e_lfanew);if (lpstNT->Signature != IMAGE_NT_SIGNATURE){MessageBox(hWinMain, szErrFormat, NULL, MB_OK);}//到此为止,该文件的验证已经完成。为PE结构文件wsprintf(szBuffer, szOut1, szFileName);_AppendInfo(szBuffer);//显示资源信息_getResource(lpMemory, lpstNT, dwFileSize);if (lpMemory)UnmapViewOfFile(lpMemory);}if (hMapFile)CloseHandle(hMapFile);}if (hFile)CloseHandle(hFile);}return;
}//RITCH控件添加文本信息--以 null 结尾的字符串
void _AppendInfo(const TCHAR * _lpsz)
{CHARRANGE stCR;//检索文本控件内文本的长度stCR.cpMin = GetWindowTextLength(hWinEdit);stCR.cpMax = stCR.cpMin;//择并替换文本控件的选定内容SendMessage(hWinEdit, EM_EXSETSEL, 0, (LPARAM)&stCR);//EM_REPLACESEL以 null 结尾的字符串的指针替换SendMessage(hWinEdit, EM_REPLACESEL, FALSE, (LPARAM)_lpsz);
}

●模块6:GetResource.c

/*
;遍历资源表项的图标组资源
;_lpFile:文件地址
;_lpRes:资源表地址
*/
#include <windows.h>
#include "info.h"extern TCHAR szFileName[MAX_PATH];	//pemian.c模块中定义
extern HANDLE hWinEdit,hFileDump;
extern HWND hWinMain;void _AppendInfo(const TCHAR * _lpsz);
DWORD RVAToOffset(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA);
DWORD GetRVASection(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA);//遍历资源表项的图标组资源:参数1:PE文件地址;参数2:资源块起始地址;
void ProcessRes(PBYTE lpFile, PBYTE lpRes, IMAGE_RESOURCE_DIRECTORY * lpResDir, DWORD dwLevel)
{const TCHAR szOut10[] = TEXT("资源表中有图标组%d个。\r\n")TEXT("----------------------------------------------------------------\r\n\r\n");const TCHAR szLevel3[] = TEXT("图标组%4d所在文件位置:0x%08x  资源长度:%d\r\n");const TCHAR szNoIconArray[] = TEXT("资源表中没有发现图标组!");static TCHAR szBuffer[1024];static TCHAR szResName[256];IMAGE_RESOURCE_DIRECTORY * lpstRES_DIR;//资源目录IMAGE_RESOURCE_DIRECTORY_ENTRY * lpstRES_DIR_ENT;//目录项IMAGE_RESOURCE_DATA_ENTRY * lpstRES_DATA_ENT;int number;	//资源数量DWORD dwNextLevel;DWORD lpAddr, lpAddr2;DWORD IDname, dwTemp1, dwTemp2, dwTemp3;DWORD dwICO = 0 ; //ICO文件的个数dwNextLevel = dwLevel + 1;//检查资源目录表,得到资源目录项的数量lpstRES_DIR = lpResDir;number = lpstRES_DIR->NumberOfIdEntries + lpstRES_DIR->NumberOfNamedEntries;
//IMAGE_RESOURCE_DIRECTORY结构后面紧跟着是IMAGE_RESOURCE_DIRECTORY_ENTRY结构lpstRES_DIR_ENT = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)((PBYTE)lpstRES_DIR + sizeof(IMAGE_RESOURCE_DIRECTORY));//循环处理每个资源目录项while (number--){RtlZeroMemory(szBuffer, sizeof(szBuffer));//OffsetToData字段最高位为1,后七位指向下层目录块的起始地址IMAGE_RESOURCE_DIRECTORY结构lpAddr = lpstRES_DIR_ENT->OffsetToData;if (lpAddr & 0x80000000){lpAddr &= 0x7fffffff;lpAddr += (DWORD)lpRes;//第一层:资源类型IDname = lpstRES_DIR_ENT->Name;//目录项名称字符串或ID//最高位为1时,低7位值作为指向UNICODE编码的资源名IAMGE_RESOURCE_STRING_U结构//如果是按名称定义的资源类型,跳过 if (IDname & 0x80000000){goto _next;}//高位为0时,表示字段的值作为ID使用else{if (IDname == 0x0e)	//判断编号是否为图标组{//移动到第二级目录//计算目录项的个数lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY*)lpAddr;dwICO = lpstRES_DIR->NumberOfNamedEntries + 
lpstRES_DIR->NumberOfIdEntries;wsprintf(szBuffer, szOut10, dwICO);_AppendInfo(szBuffer);//跳过第二级目录头定位到第二级目录项lpAddr2 = lpAddr + sizeof(IMAGE_RESOURCE_DIRECTORY);dwTemp1 = 0;while (dwICO--){lpstRES_DIR_ENT = 
(IMAGE_RESOURCE_DIRECTORY_ENTRY*)lpAddr2;//直接访问到数据,获取数据在文件的偏移及大小dwTemp1++;lpAddr = lpstRES_DIR_ENT->OffsetToData;if (lpAddr & 0x80000000){lpAddr &= 0x7fffffff;	//最高位为1lpAddr += (DWORD)lpRes;	//第三级lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY*)lpAddr;//移动到第三级目录,假设目录项数量都为1lpAddr += sizeof(IMAGE_RESOURCE_DIRECTORY);lpstRES_DIR_ENT = 
(IMAGE_RESOURCE_DIRECTORY_ENTRY*)lpAddr;//地址指向数据项lpAddr = lpstRES_DIR_ENT->OffsetToData + 
(DWORD)lpRes;lpstRES_DATA_ENT = 
(IMAGE_RESOURCE_DATA_ENTRY*)lpAddr;dwTemp3 = lpstRES_DATA_ENT->Size;dwTemp2 = RVAToOffset((IMAGE_DOS_HEADER *)lpFile, 
lpstRES_DATA_ENT->OffsetToData);wsprintf(szBuffer, szLevel3, dwTemp1, dwTemp2,dwTemp3);_AppendInfo(szBuffer);//处理单个ICO文件,参数1:文件开始,参数2:资源表开始//参数3:PE ICO头开始,参数4:编号,参数5:PE ICO头大小_getIcoData(lpFile, lpRes, dwTemp1, dwTemp2, 
dwTemp3);}lpAddr2 += sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY);}goto _next;}elsegoto _next;}}_next:lpstRES_DIR_ENT++;}if (dwICO == 0)_AppendInfo(szNoIconArray);
}
//获取PE文件的资源信息
void _getResource(PBYTE lpFile, IMAGE_NT_HEADERS * _lpPeHead, int _dwSize)
{const TCHAR szNoResource[] = TEXT("\r\n未发现资源表!");static TCHAR szBuffer[256];static TCHAR szSectionName[16];static TCHAR szNameB[256];static TCHAR szNameW[256];IMAGE_NT_HEADERS32 * lpstNT32;		//PE32文件头IMAGE_NT_HEADERS64 * lpstNT64;		//PE64文件头IMAGE_RESOURCE_DIRECTORY * lpstRES_DIR;//资源目录DWORD rva, address;lpstNT32 = _lpPeHead;lpstNT64 = (IMAGE_NT_HEADERS64 *)_lpPeHead;//检测是否存在资源--数据目录第2项if (lpstNT64->OptionalHeader.Magic == 0x020B) //64位PE文件{//数据目录项的第3项rva = lpstNT64->OptionalHeader.DataDirectory[2].VirtualAddress; }elserva = lpstNT32->OptionalHeader.DataDirectory[2].VirtualAddress;if (!rva){_AppendInfo(szNoResource);return;}//求资源表在文件的偏移address = RVAToOffset((IMAGE_DOS_HEADER *)lpFile, rva);if (!address)return;
//资源目录的实际地址lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY *)(address + (DWORD)lpFile); /*显示所有资源目录块的信息; 传入的两个参数分别表示; 1、文件头位置; 2、资源表位置*/ProcessRes(lpFile, (PBYTE)lpstRES_DIR, lpstRES_DIR, 1);
}

●模块7:GetIconData.c

/*
;通过PE ICO头获取ICO数据
;参数1:文件开始
;参数2:资源表开始
;参数3:PE ICO头开始
;参数4:编号(由此构造磁盘文件名g12.ico)
;参数5:PE ICO头大小
*/
#include <windows.h>
#include "info.h"extern TCHAR szFileName[MAX_PATH];	//pemian.c模块中定义
extern HANDLE hFile;
extern HWND hWinMain;// 文件中的ICO头部
typedef struct  
{byte bWidth ;			//宽度byte bHeight ;			//高度byte bColorCount ;		//颜色数byte bReserved ;		//保留字,必须为0WORD wPlanes ;			//调色板WORD wBitCount ;		//每个像素的位数DWORD dwBytesInRes ;	//资源长度DWORD dwImageOffset ;	//资源在文件偏移
}ICON_DIR_ENTRY;//PE中ICO头部
typedef struct  
{byte bWidth;		//宽度byte bHeight;		//高度byte bColorCount;	//颜色数byte bReserved;		//保留字,必须为0WORD wPlanes;		//调色板WORD wBitCount;		//每个像素的位数DWORD dwBytesInRes;	//资源长度WORD dwImageOffset;	//资源在文件偏移
}PE_ICON_DIR_ENTRY;//处理单个ICO文件
void _getIcoData(PBYTE lpFile, PBYTE lpRes, DWORD number, DWORD off, DWORD size)
{const TCHAR szFile[] = TEXT("  >>新建文件%s\r\n");const TCHAR szOut11[] = TEXT("g%d.ico");const TCHAR szOut13[] = TEXT("  >>图标组%4d中共有图标:%d个。\r\n");const TCHAR szICOHeader[] = TEXT("  >> 完成ICO头部信息 \r\n");TCHAR szFileName1[MAX_PATH];static TCHAR szBuffer[256];DWORD dwTemp, dwCount, dwTemp1;DWORD dwForward,lpMemory;DWORD dwIcoDataSize;	//图标数据的大小PE_ICON_DIR_ENTRY * lpPE_ICON_DIR_ENTRY;ICON_DIR_ENTRY lpIconDE;//PE_ICON_DIR_ENTRY lpPEIconDE;WORD order;DWORD lpAddr;wsprintf(szFileName1,szOut11,number);wsprintf(szBuffer,szFile,szFileName1);_AppendInfo(szBuffer);//将内存写入文件以供检查hFile = CreateFile(szFileName1,GENERIC_WRITE,FILE_SHARE_READ,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);//定位文件指针lpMemory = (DWORD)lpFile + off;//写入6个字节文件头WriteFile(hFile, (PBYTE)lpMemory, 6, &dwTemp, NULL);//求出图标组包含图标的个数dwCount = *(PWORD)((PBYTE)lpMemory + 4);wsprintf(szBuffer,szOut13,number,dwCount);_AppendInfo(szBuffer);//求第一个图标数据在文件中的偏移dwForward = dwCount * 2 + size;//每一个记录多2个字节//定位到ICO图标项起始lpPE_ICON_DIR_ENTRY = (PE_ICON_DIR_ENTRY *)((PBYTE)lpMemory + 6);dwIcoDataSize = 0;dwTemp1 = dwCount;while (dwTemp1--){//将PE_ICON_DIR_ENTRY结构中的大部分字段赋值,除最后一个字段外lpIconDE.bWidth = lpPE_ICON_DIR_ENTRY->bWidth;lpIconDE.bHeight = lpPE_ICON_DIR_ENTRY->bHeight;lpIconDE.bColorCount = lpPE_ICON_DIR_ENTRY->bColorCount;lpIconDE.bReserved = lpPE_ICON_DIR_ENTRY->bReserved;lpIconDE.wPlanes = lpPE_ICON_DIR_ENTRY->wPlanes;lpIconDE.wBitCount = lpPE_ICON_DIR_ENTRY->wBitCount;lpIconDE.dwBytesInRes = lpPE_ICON_DIR_ENTRY->dwBytesInRes;//该值需要修正,记录图标数据在文件偏移。//第一个图标的该值是文件ICO头大小//以后的图标的该值是上一个加上数据长度lpIconDE.dwImageOffset = dwForward + dwIcoDataSize;WriteFile(hFile, &lpIconDE, sizeof(ICON_DIR_ENTRY),&dwTemp,NULL);//为下一次计算地址做准备dwIcoDataSize = lpPE_ICON_DIR_ENTRY->dwBytesInRes;lpPE_ICON_DIR_ENTRY++;}//该循环结束,所有的头部信息已经完毕_AppendInfo(szICOHeader);//开始下一个循环,将所有图标数据写入文件lpAddr = (DWORD)lpMemory + 6;dwTemp1 = dwCount;while (dwTemp1--){lpPE_ICON_DIR_ENTRY = (PE_ICON_DIR_ENTRY *)lpAddr;//写入文件图标数据order = lpPE_ICON_DIR_ENTRY->dwImageOffset;//取得图标的顺号//返回eax为图标数据大小_getFinnalData(lpFile, lpRes, order);//lpAddr += sizeof(PE_ICON_DIR_ENTRY);//16个字节--错误lpAddr += 14;}CloseHandle(hFile);
}
//将图标数据写入文件
/*
;参数:lpFile 文件内存起始地址
;参数: lpRes 资源表起始地址
;参数:nubmer为图标顺号
*/
int _getFinnalData(PBYTE lpFile, PBYTE lpRes, DWORD number)
{static TCHAR szBuffer[1024];static TCHAR szResName[256];DWORD lpMem, dwTemp,dwTemp1, dwTemp2, dwTemp3;DWORD dwICO = 0; //ICO文件的个数int count = 0;	//图标数据大小int counter;	//资源数量DWORD lpAddr, IDname;IMAGE_RESOURCE_DIRECTORY * lpstRES_DIR;//资源目录IMAGE_RESOURCE_DIRECTORY_ENTRY * lpstRES_DIR_ENT;//目录项IMAGE_RESOURCE_DATA_ENTRY * lpstRES_DATA_ENT;const TCHAR szLevel31[] = TEXT("  >> 图标%4d所在文件位置:0x%08x  资源长度:%d\r\n");const TCHAR szFinished[] = TEXT("  >> 完成写入。\r\n\r\n");const TCHAR szNoIconArray[] = TEXT("资源表中没有发现图标组!");//计算目录项的个数lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY *)lpRes;counter = lpstRES_DIR->NumberOfIdEntries + lpstRES_DIR->NumberOfNamedEntries;
//IMAGE_RESOURCE_DIRECTORY结构后面紧跟着是IMAGE_RESOURCE_DIRECTORY_ENTRY结构lpstRES_DIR_ENT = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)((PBYTE)lpstRES_DIR + sizeof(IMAGE_RESOURCE_DIRECTORY));//循环处理每个资源目录项while (counter--){RtlZeroMemory(szBuffer, sizeof(szBuffer));
//OffsetToData字段最高位为1,后七位指向下层目录块的起始地址IMAGE_RESOURCE_DIRECTORY结构lpAddr = lpstRES_DIR_ENT->OffsetToData;if (lpAddr & 0x80000000){lpAddr &= 0x7fffffff;lpAddr += (DWORD)lpRes;//第一层:资源类型IDname = lpstRES_DIR_ENT->Name;//目录项名称字符串或ID
//最高位为1时,低7位值作为指向UNICODE编码的资源名IAMGE_RESOURCE_STRING_U结构//如果是按名称定义的资源类型,跳过 if (IDname & 0x80000000){goto _next;}//高位为0时,表示按编号定义的资源类型else{//第一层指向了资源类别if (IDname == 0x03)	//判断编号是否为图标组{//移动到第二级目录//计算目录项的个数lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY*)lpAddr;dwICO = lpstRES_DIR->NumberOfNamedEntries + 
lpstRES_DIR->NumberOfIdEntries;//跳过第二级目录头定位到第二级目录项lpAddr += sizeof(IMAGE_RESOURCE_DIRECTORY);lpstRES_DIR_ENT = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)lpAddr;dwTemp1 = 0;while (dwICO--){//直接访问到数据,获取数据在文件的偏移及大小dwTemp1++;//判断序号是否和指定的一致if (dwTemp1 != number)goto _loop;//如果一致,则继续查找数据lpAddr = lpstRES_DIR_ENT->OffsetToData;if (lpAddr & 0x80000000){lpAddr &= 0x7fffffff;	//最高位为1lpAddr += (DWORD)lpRes;	//第三级lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY*)lpAddr;//移动到第三级目录,假设目录项数量都为1lpAddr += sizeof(IMAGE_RESOURCE_DIRECTORY);lpstRES_DIR_ENT = 
(IMAGE_RESOURCE_DIRECTORY_ENTRY*)lpAddr;//地址指向数据项lpAddr = lpstRES_DIR_ENT->OffsetToData + 
(DWORD)lpRes;lpstRES_DATA_ENT = 
(IMAGE_RESOURCE_DATA_ENTRY*)lpAddr;dwTemp3 = lpstRES_DATA_ENT->Size;dwTemp2 = RVAToOffset((IMAGE_DOS_HEADER *)lpFile,lpstRES_DATA_ENT->OffsetToData);wsprintf(szBuffer, szLevel31, dwTemp1, dwTemp2, 
dwTemp3);_AppendInfo(szBuffer);//将dwTemp2开始的dwTemp3个字节写入文件lpMem = (DWORD)lpFile + dwTemp2;WriteFile(hFile, (PBYTE)lpMem, dwTemp3, &dwTemp, 
NULL);_AppendInfo(szFinished);count = 1;return count;}_loop:lpstRES_DIR_ENT++;}goto _next;}elsegoto _next;}}_next:lpstRES_DIR_ENT++;}if (dwICO == 0)_AppendInfo(szNoIconArray);return count;
}

运行:

图7-8 提取图标

  总结

上述程序假设图标是由多个图标数据组成,即资源表中一定存在图标组。

ICO头部分+ICO项描述在资源图标组里定义,每一部分数据则在资源图标里定义。程序 首先检测是否存在图标组资源,如果不存在则退出;如果存在,就定位到图标组资源数据,通过将字段编号(1个字)修改为偏移(2个字),重新组合ICO头部使其符合ICO文件头部要求。最后,分別读取各部分图标资源数据,顺序连接到ICO头部后即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/457414.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux -- 共享内存(2)

目录 命令 ipcs -m &#xff1a; 命令 ipcrm -m shmid&#xff1a; 共享内存的通信&#xff1a; 为什么共享内存更高效&#xff1f; 代码&#xff1a; ShmClient.cc&#xff1a; ShmServer.cc&#xff1a; 结果&#xff1a; 如何让共享内存实现同步&#xff1f; 代码&a…

基于SSM的BBS社区论坛系统源码

运行环境&#xff1a;ideamysql5.7jdk8maven 使用技术&#xff1a;ssmmysqlshirolayui 功能模块&#xff1a;用户管理、模板管理、帖子管理、公告管理、权限管理等

echarts:导入excel生成桑葚图

前言 前两天帮别人实现了一个小功能&#xff0c;主要是选择excel文件&#xff0c;读取里面的数据&#xff0c;将数据生成桑葚图 echarts官方桑葚图案例 实现 因为就是一个单纯的html文件&#xff0c;用到的库都是通过CDN的方式加载的&#xff0c;会有一些慢 <!DOCTYPE …

IPC 进程间通信 信号量集合 Linux环境 C语言实现

只用于多进程间的并发控制 一个信息量集合(信号量集 或 信息量数组)中顺序存储着多个信号量 相关接口函数&#xff1a; 实际项目&#xff0c;直接调用semop函数来作为某个或某几个信号量的PV操作函数会很不方便&#xff0c;因此会对信号量集合的接口进行二次封装&#xff0c;封…

AI实操Excel:在Excel中学习人工智能基础算法

人工智能&#xff08;AI&#xff09;已经成为当今科技领域最热门的话题之一&#xff0c;但对于初学者来说&#xff0c;直接上手复杂的编程和算法可能会感到有些困难。今天&#xff0c;我要向大家介绍一个非常实用的工具——AI实操Excel&#xff0c;这是一个通过Excel实现人工智…

「二叉树进阶题解:构建、遍历与结构转化全解析」

文章目录 根据二叉树创建字符串思路代码 二叉树的层序遍历思路代码 二叉树的最近公共祖先思路代码 二叉搜索树与双向链表思路代码 从前序与中序遍历序列构造二叉树思路代码 总结 根据二叉树创建字符串 题目&#xff1a; 样例&#xff1a; 可以看见&#xff0c;唯一特殊的就…

影刀RPA实战:常见实用功能指令

1. 电脑锁屏与解屏 在实际工作中&#xff0c;我们为了自身工作电脑数据文件的安全&#xff0c;都会为电脑设置密码&#xff0c;当我们离开电脑时&#xff0c;直接锁屏&#xff0c;即使不手动锁屏&#xff0c;也会在一定时间内自动锁屏。 如果你的工作是影刀RPA帮你自动化处理…

Spring Boot驱动的厨艺社交平台设计与实现

5 系统实现 5.1食材分类管理 管理员管理食材分类&#xff0c;可以添加&#xff0c;修改&#xff0c;删除食材分类信息。下图就是食材分类管理页面。 图5.1 食材分类管理页面 5.2 用户信息管理 管理员管理用户信息&#xff0c;可以添加&#xff0c;修改&#xff0c;删除用户信…

kafka 分布式(不是单机)的情况下,如何保证消息的顺序消费?

大家好&#xff0c;我是锋哥。今天分享关于【kafka 分布式&#xff08;不是单机&#xff09;的情况下&#xff0c;如何保证消息的顺序消费?】面试题&#xff1f;希望对大家有帮助&#xff1b; kafka 分布式&#xff08;不是单机&#xff09;的情况下&#xff0c;如何保证消息的…

量子变分算法 (python qiskit)

背景 变分量子算法是用于观察嘈杂的近期设备上的量子计算效用的有前途的候选混合算法。变分算法的特点是使用经典优化算法迭代更新参数化试验解决方案或“拟设”。这些方法中最重要的是变分量子特征求解器 (VQE)&#xff0c;它旨在求解给定汉密尔顿量的基态&#xff0c;该汉密尔…

mac 上使用 cmake 构建包含 OpenMP 的项目

安装依赖 # clang 默认不支持 -fopenmp&#xff0c;因为它没有内置 OpenMP 支持。 # 为了解决这个问题&#xff0c;需要安装 libomp 并配置 clang 使用 libomp brew install libomp# macOS 自带的 clang 编译器被修改过&#xff0c;默认禁用了 OpenMP&#xff0c; # 而不支持 …

【K8S系列】Kubernetes Service 基础知识 详细介绍

在 Kubernetes 中&#xff0c;Service 是一种抽象的资源&#xff0c;用于定义一组 Pod 的访问策略。它为这些 Pod 提供了一个稳定的访问入口&#xff0c;解决了 Pod 可能频繁变化的问题。本文将详细介绍 Kubernetes Service 的类型、功能、使用场景、DNS 和负载均衡等方面。 1.…

class 36 二叉树高频题目 - 上 (不含有树形dp)

1. BFS 的两种方式 如下图, 是一个二叉树. 我们需要按照层的方式来遍历这棵树. 1.1 使用 JDK 自带的类实现(链表实现, 经典 BFS) 首先我们实现一个队列, 这个队列从头进, 从尾出.然后将根节点放入其中, 然后将放入的节点弹出,然后继续验证弹出的节点有没有左孩子, 若是有, 将…

【HTML】之form表单元素详解

HTML表单是网页与用户交互的关键组成部分&#xff0c;它允许用户输入数据并将数据提交到服务器进行处理。本文将全面详细地介绍HTML表单的各个方面&#xff0c;从基础元素到高级用法&#xff0c;并提供丰富的代码示例和中文注释&#xff0c;帮助你彻底掌握表单的使用。 1. 表单…

强大!Spring Boot 3.3 集成 PDFBox 轻松实现电子签章功能!

强大&#xff01;Spring Boot 3.3 集成 PDFBox 轻松实现电子签章功能&#xff01; 随着数字化办公和电子合同的普及&#xff0c;PDF 文档已经成为很多业务场景中的标准文件格式。为了确保文档的安全性和法律效力&#xff0c;电子签章技术应运而生。电子签章不仅可以证明文件的…

视频美颜平台的搭建指南:基于直播美颜SDK的完整解决方案

众所周知&#xff0c;直播美颜SDK是实现视频美颜功能的核心。本文将详细解析如何基于直播美颜SDK搭建一个完整的视频美颜平台。 一、视频美颜SDK的核心功能 直播美颜SDK作为平台的技术核心&#xff0c;能够提供丰富的美颜效果和稳定的视频处理能力。通常&#xff0c;SDK具备以…

传输层TCP

报头 1.报头和有效载荷如何分离将&#xff0c;有效载荷向上交付&#xff1f; tcp有个标准报头长度为20&#xff0c;那是不是以为我们可以像udp一样分离依靠报头大小去分离&#xff0c;我们仔细去看我们报头中还有个选项没包含到。 我们还有个首部长度&#xff0c;四位可以表…

【Axure高保真原型】分级树筛选中继器表格

今天和大家分享分级树筛选中继器表格的原型模板&#xff0c;点击树的箭头可以展开或者收起子级内容&#xff0c;点击内容&#xff0c;可以筛选出该内容及子级内容下所有的表格数据。左侧的树和右侧的表格都是用中继器制作的&#xff0c;所以使用也很方便&#xff0c;只需要在中…

SwiftUI:单个App支持设置多语言

SwiftUI 全新多语言方案 简化本地化的字符串- WWDC21 - 视频 本地化您的SwiftUI app - WWDC21 - 视频 构建全球化App&#xff1a;本地化的示例- WWDC22 - 视频 构建支持多语言的App - WWDC24 - 视频 单个App支持设置多语言 工程 Info.plist里添加 键值UIPrefersShowingLangua…

论1+2+3+4+... = -1/12 的不同算法

我们熟知自然数全加和&#xff0c; 推导过程如下&#xff0c; 这个解法并不难&#xff0c;非常容易看懂&#xff0c;但是并不容易真正理解。正负交错和无穷项计算&#xff0c;只需要保持方程的形态&#xff0c;就可以“预知”结果。但是这到底说的是什么意思&#xff1f;比如和…