《Windows PE》7.4 资源表应用

news2024/10/27 18:22:14

本节我们将通过两个示例程序,演示对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;			//保留字,必须为0
	WORD wPlanes;			//调色板
	WORD wBitCount;			//每个像素的位数
	DWORD dwBytesInRes;		//资源长度
	DWORD dwImageOffset;	//资源在文件偏移
}ICON_DIR_ENTRY;

typedef struct
{
	WORD idReserved;			//保留字,必须为0
	WORD idType;				//资源类别,如果是1表示为ICO文件
	WORD idCount;				//图标数量
	//ICON_DIR_ENTRY idEntries;	//图标项,一个图标一项
}ICON_DIR;

//PE中ICO头部
typedef struct
{
	byte bWidth;			//宽度
	byte bHeight;			//高度
	byte bColorCount;		//颜色数
	byte bReserved;		//保留字,必须为0
	WORD wPlanes;		//调色板
	WORD wBitCount;		//每个像素的位数
	DWORD dwBytesInRes;	//资源长度
	WORD nID;			//资源在文件序号
}PE_ICON_DIR_ENTRY;
typedef struct
{
	WORD idReserved;	//保留字,必须为0
	WORD 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);
	else
		goto 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;	//最高位为1
							lpAddr += (DWORD)lpRes;	//第三级
							lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY*)lpAddr;
							//移动到第三级目录,假设目录项数量都为1
							lpAddr += 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;
				}
				else
					goto _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; 
	}
	else
		rva = 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 ;		//保留字,必须为0
	WORD wPlanes ;			//调色板
	WORD wBitCount ;		//每个像素的位数
	DWORD dwBytesInRes ;	//资源长度
	DWORD dwImageOffset ;	//资源在文件偏移
}ICON_DIR_ENTRY;

//PE中ICO头部
typedef struct  
{
	byte bWidth;		//宽度
	byte bHeight;		//高度
	byte bColorCount;	//颜色数
	byte bReserved;		//保留字,必须为0
	WORD 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;	//最高位为1
							lpAddr += (DWORD)lpRes;	//第三级
							lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY*)lpAddr;
							//移动到第三级目录,假设目录项数量都为1
							lpAddr += 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;
				}
				else
					goto _next;
			}
		}
	_next:
		lpstRES_DIR_ENT++;
	}
	if (dwICO == 0)
		_AppendInfo(szNoIconArray);
	return count;
}

运行:

图7-8 提取图标

  总结

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Telephony中ITelephony的AIDL调用关系

以Android14.0源码讲解 ITelephony来自framework下的com.android.internal.telephony包下 frameworks/base/telephony/java/com/android/internal/telephony/ITelephony.aidl这个接口用于与Phone交互的界面&#xff0c;主要由TelephonyManager类使用&#xff0c;一些地方仍在…

开拓鸿蒙测试新境界,龙测科技引领自动化测试未来

在当今科技舞台上&#xff0c;鸿蒙 OS 以非凡先进性强势登场&#xff0c;打破传统操作系统格局&#xff0c;为软件测试领域带来全新机遇与艰巨挑战。 一、鸿蒙 OS 的辉煌崛起 &#xff08;一&#xff09;壮丽发展历程与卓越市场地位 鸿蒙 OS 的发展如波澜壮阔的史诗。2023 年…

音视频开发之旅(98) -潜扩散模型(Latent Diffusion Model)原理及源码解析

目录 1.背景 2. 潜扩散模型&#xff08;Latent Diffusion Model&#xff09;原理 3. 应用场景 4. 推理源码解析 5. 资料 一、背景 前面我们分析扩散模型&#xff08;Diffusion Model&#xff09;了解到&#xff0c;它通过向数据中添加噪声&#xff0c;然后训练一个去噪模…

SSM学习day01 JS基础语法

一、JS基础语法 跟java有点像&#xff0c;但是不用注明数据类型 使用var去声明变量 特点1&#xff1a;var关键字声明变量&#xff0c;是为全局变量&#xff0c;作用域很大。在一个代码块中定义的变量&#xff0c;在其他代码块里也能使用 特点2&#xff1a;可以重复定义&#…

【mysql进阶】4-6. InnoDB 磁盘文件

InnoDB 磁盘⽂件 1 InnoDB存储引擎包含哪些磁盘⽂件&#xff1f; &#x1f50d; 分析过程 ✅ 解答问题 InnoDB的磁盘⽂件主要是表空间⽂件和其他⽂件&#xff0c;表空间包括&#xff1a;系统表空间、独⽴表空间、通⽤表空间、临时表空间和撤销表空间&#xff1b;其他⽂件有重做…

XQT_UI 组件|03 |加载组件 XQtLoading

XQtLoading 使用文档 简介 XQtLoading 是一个自定义的加载动画组件&#xff0c;旨在为用户提供可配置的旋转花瓣动画效果。它可以在应用程序中用于指示加载状态&#xff0c;提升用户体验。 特征 可配置性&#xff1a;用户可以根据需求调整旋转周期、缩放周期、最大/最小缩放…

JavaScript part2

一.前言 前面我们讲了一下js的基础语法&#xff0c;但是这些还是远远不够的&#xff0c;我们要想操作标签&#xff0c;实现一个动态且好看的页面&#xff0c;就得学会BOM和DOM&#xff0c;这些都是浏览器和页面的&#xff0c;这样我们才能实现一个好看的页面 二.BOM对象 BOM…

golang将指针传给cgo后还能被回收吗?

问题&#xff1a; 如果把golang分配的变量&#xff0c;其指针通过cgo传给c&#xff0c;并被c存储&#xff0c;那这个变量还能被gc回收吗&#xff1f; 实验代码&#xff1a; test_memory_leak.go package main/* #include <stdlib.h> #include <string.h> #incl…

yolov11的onnx模型C++ 调用

yolov11的onnx模型C调用 效果图一、python调用二、onnx模型导出三、python的onnx调用调用检测模型调用分割模型 四、C的onnx模型调用五 、视频流的检测后续 效果图 一、python调用 本文只记录生成的yolov11模型如何调用&#xff0c;其他可参考各种yolov11博客 模型下载&#x…

Spring Boot 应用开发全攻略:从入门到精通

Spring Boot 应用开发全攻略&#xff1a;从入门到精通 引言 在当今快速发展的软件开发领域&#xff0c;Spring Boot 作为一种快速开发框架&#xff0c;凭借其简洁、易用的特性&#xff0c;赢得了开发者的广泛青睐。无论是微服务架构还是传统的单体应用&#xff0c;Spring Boo…

Redis 单机、主从、哨兵和集群架构详解和搭建

目录 前言 单机部署 检查安装 gcc 环境 下载安装 Redis 启动 Redis 关闭 Redis 配置Redis 主从部署 整体架构图 主从复制配置 重启 Redis 验证 主从复制的作⽤ 主从复制缺点 哨兵部署&#xff08;Sentinel&#xff09; 整体架构图 哨兵模式配置 启动哨兵 验证…

首席数据官和首席数据分析官

根据分析人士的预测&#xff0c;首席数据官&#xff08;CDO&#xff09;和首席数据分析官&#xff08;CDAO&#xff09;必须更有效地展示他们对企业和AI项目的价值&#xff0c;以保障其在高管层的地位。Gartner的最新报告指出&#xff0c;CDO和CDAO在AI时代需要重新塑造自身定位…

ElegantBook:优美的 LATEX 书籍模板(中文的latex模版)

关注B站可以观看更多实战教学视频&#xff1a;hallo128的个人空间 ElegantBook&#xff1a;优美的 LATEX 书籍模板&#xff08;中文的latex模版&#xff09; Github地址&#xff1a;https://github.com/ElegantLaTeX/ElegantBook使用说明文档&#xff1a;https://static.latexs…

C++11实践指北

C11&#xff1a;书、在线工具、库。 书 1. 《现代C语言核心特性解析》 覆盖 C11~C20 特性的讲解。 视频跟读&#xff1a;https://www.bilibili.com/video/BV1nN4y1j7fv 现代CPP随笔_0CCh - 每天5分钟了解现代C新特性 2. 《C Primer》第五版 基于 C11 的 C 入门书。 正在看…

故障诊断 | CNN-ResNets滚动轴承故障诊断实例代码

故障诊断 | CNN-ResNets滚动轴承故障诊断实例代码 目录 故障诊断 | CNN-ResNets滚动轴承故障诊断实例代码效果一览基本介绍程序设计参考资料 效果一览 基本介绍 CNN-ResNets&#xff08;卷积神经网络-残差网络&#xff09;在滚动轴承故障诊断中是一种常用的方法。这种方法利用…

使用Angular构建动态Web应用

&#x1f496; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4bb; Gitee主页&#xff1a;瑕疵的gitee主页 &#x1f680; 文章专栏&#xff1a;《热点资讯》 使用Angular构建动态Web应用 1 引言 2 Angular简介 3 安装Angular 4 创建Angular项目 5 设计应用结构 6 创建组件 7 …

Python小游戏14——雷霆战机

首先&#xff0c;你需要确保安装了Pygame库。如果你还没有安装&#xff0c;可以使用pip来安装&#xff1a; bash pip install pygame 代码如下&#xff1a; python import pygame import sys import random # 初始化Pygame pygame.init() # 设置屏幕大小 screen_width 800 scr…

云原生笔记

#1024程序员节|征文# 单页应用(Single-Page Application&#xff0c;SPA) 云原生基础 云原生全景内容宽泛&#xff0c;以至于刚开始就极具挑战性。 云原生应用是高度分布式系统&#xff0c;它们存在于云中&#xff0c;并且能够对变化保持韧性。系统是由多个服务组成的&#…

java-JVM面试问题-2024

1、简单介绍下虚拟机内存模型&#xff1f; VM由三部分组成&#xff1a;类加载子系统、运行时数据区、执行引擎 类加载子系统&#xff1a;通过类加载机制加载类的class文件&#xff0c;如果该类是第一次加载&#xff0c;会执行加载、验证、解析。只负责class文件的加载&#x…

基于neo4j的医疗问诊系统

当你身体不适时&#xff0c;想要找到准确的答案却经常遇到模棱两可的答复&#xff0c;糟心吗&#xff1f;现在&#xff0c;基于neo4j的智能医疗问诊系统为你带来全新体验&#xff01;我们设计了一个具备自动化问答功能的医疗系统&#xff0c;帮助用户快速获取专业的健康知识答案…