win32程序编译完成后会有默认的图标,很丑;
也可以自己添加图标;
1.给win32程序添加图标
首先下载.ico格式的图标,放入工程目录中;
打开xx.rc文件 -》右键选中xx.rc -》插入 -》选图标Icon -》引入 -》选自己下载的.icon
添加完成后,可以在resource.h中看到图标id的宏定义;
接下来加载图标到程序中;
需要用到LoadIcon函数:
HICON hIcon; //图标句柄 hIcon = LoadIcon (hAppInstance, MAKEINTRESOURCE (IDI_ICON)); hAppInstance ->应用程序句柄; IDI_ICON ->图标的宏定义;第二个参数为字符串,可以用MAKEINTRESOURCE将数字转成字符串指针;
知道了如何加载图标到程序,接下来就是考虑何时加载图标;
如果是普通窗口,一般是在窗口创建时加载;也就是将图标加载放WM_CREATE事件中;
现在用的是对话框窗口,对话框窗口创建时的事件为:WM_INITDIALOG;
在回调函数中添加一个事件:
case WM_INITDIALOG : hIcon = LoadIcon (hAppInstance, MAKEINTRESOURCE (IDI_ICON)); //设置图标 SendMessage(hDlg,WM_SETICON,ICON_BIG,(DWORD)hIcon); SendMessage(hDlg,WM_SETICON,ICON_SMALL,(DWORD)hIcon); return TRUE;
用SendMessage函数给对话框窗口发送设置图标的消息:WM_SETICON;
程序有大图标和小图标,任务栏的是大图标,其它地方是小图标;
踩坑:
error RC2176 : old DIB in res/AES.ico; pass it through SDKPAINT
原因:
这是由于载入的资源文件(****.ico)是真彩色,即3个字节的,而VC6.0只支持256色,因此出现错误!
解决:
一个.ico文件由多个图层组成;
用IconWorkShop打开图标,将不符合要求的图层即非256色的删除,然后保存;
代码:
// rcWin32.cpp : Defines the entry point for the application. // #include "stdio.h" #include "windows.h" #include "resource.h" HINSTANCE hAppInstance; BOOL CALLBACK DialogProc( HWND hwndDlg, // handle to dialog box UINT uMsg, // message WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ) { HICON hicon_big; HICON hicon_small; switch(uMsg) { case WM_INITDIALOG : hicon_big = LoadIcon (hAppInstance, MAKEINTRESOURCE (IDI_ICON_BIG)); hicon_small = LoadIcon (hAppInstance, MAKEINTRESOURCE (IDI_ICON_SMALL)); //设置图标 SendMessage(hwndDlg,WM_SETICON,ICON_BIG,(DWORD)hicon_big); SendMessage(hwndDlg,WM_SETICON,ICON_SMALL,(DWORD)hicon_small); return TRUE; case WM_COMMAND : switch (LOWORD (wParam)) { case btn_ok : MessageBox(NULL,TEXT("OK"),TEXT("OK"),MB_OK); return TRUE; case btn_cancel: MessageBox(NULL,TEXT("bye"),TEXT("OUT"),MB_OK); EndDialog(hwndDlg, 0); return TRUE; } break ; } return FALSE ; } int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { hAppInstance = hInstance; DialogBox( hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc ); return 0; }
结果:
2.资源文件
上一步成功给exe程序添加了图标;
那么图标是如何保存在exe中的?
pe结构的数据目录的第三项为资源目录;
数据目录项结构:
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
资源目录的结构:
typedef struct _IMAGE_RESOURCE_DIRECTORY { DWORD Characteristics; //资源属性 保留 0 DWORD TimeDateStamp; //资源创建的时间 WORD MajorVersion; //资源版本号 未使用 0 WORD MinorVersion; //资源版本号 未使用 0 WORD NumberOfNamedEntries; //以名称命名的资源数量 WORD NumberOfIdEntries; //以ID命名的资源数量 // IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[]; } IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
NumberOfNamedEntries和NumberOfIdEntries这两个属性比较重要;
资源目录项结构:
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY { union { //目录项的名称、或者ID struct { DWORD NameOffset:31; DWORD NameIsString:1; }; DWORD Name; WORD Id; }; union { DWORD OffsetToData; //目录项指针 struct { DWORD OffsetToDirectory:31; DWORD DataIsDirectory:1; }; }; } IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
该结构的第一个联合占4个字节;
联合中有个结构,表示位域;也就是将一个DWORD拆分成2份;
第一个结构成员表示低31位;根据最高位的值来做不同的用途;
第二个结构成员表示最高位;用作标识;
用位运算也可以达到位域的效果;也就是说位域本质上就是编译器替我们来做位运算;
资源数据结构:
typedef struct _IMAGE_RESOURCE_DATA_ENTRY { DWORD OffsetToData;//资源数据的RVA DWORD Size;//资源数据的长度 DWORD CodePage;//代码页 DWORD Reserved;//保留字段 } IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
这个结构体是用来保存资源数据的信息,前两个有用其他的没有用
总体结构如图:
特别说明:
1】当最高位是1时,低31位是一个UNICODE指针,指向一个结构:(也就是说名称用unicode编码)
typedef struct _IMAGE_RESOURCE_DIR_STRING_U { WORD Length; WCHAR NameString[ 1 ]; } IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
2】当最高位是0时,表示字段的值作为 ID 使用
3】如何判断第一位的值?
printf("%x\n",(pResourceEntry[i].Name & 0x80000000) == 0x80000000); //用位运算的方式 printf("%x\n",pResourceEntry[i].NameIsString == 1); //用位域的方式
OffsetToData的含义:
最高位如果为1,低31位 + 资源地址 == 下一层目录节点的起始位置
第一层、第二层全为1.
最高位如果为0,指向 IMAGE_RESOURCE_DATA_ENTRY
第三层为0
3.从pe文件中找资源
目标:从pe文件IconDemo.exe中找到图标;
1)找到pe文件的资源目录的地址:
用pe工具打开IconDemo.exe;
查看数据目录,可以看到资源目录的RVA为2b000;
查看节表,找到对应的FOA:
可以看到2b000对应的foa为29000;
用二进制工具winhex打开IconDemo.exe,找到资源目录29000处,即为资源目录区;
2)分析第一层
第一层表示资源的类型;
系统预定义的资源类型有16种:例如图标、对话框等等;
因为当时认为16中资源够用了就只预定了16种;其它类型的资源也是可以指定的;
29000~2900F为第一个资源目录结构;
可以看到:
NumberOfNamedEntries = 0;
NumberOfIdEntries = 3;
说明该pe文件共有3种资源文件,并且都以id命名;
29010~29018为第一层的第一个资源目录项结构;
分析:
资源目录项结构由两个4字节的联合组成;
第一个联合为000002;
最高位为0说明是按id 命名的,因此低31位是id而不是unicode字符串的指针;
id为2,说明该资源类型为图标;
第二个联合为:80000028;
最高位为1,说明是第1层或第2层;
低31位为28;
该种类型的资源第二层的资源目录起始地址 = 29000 +28 = 29028;
3)分析第二层
第二层表示资源的编号;
上一步找到了类型为图标的资源第二层的起始地址为29028;
根据资源目录结构分析;
可以知道:一共有8个图标
每个图标的编号分别为1~8;
分析第一个图标的资源目录项:
第二个联合值为8000B0;
最高为为1,说明是第一层或第二层;
低31位为b0,第三层的起始地址 = 29000 + b0 = 290b0;
4)分析第三层
找到地址290b0处;
从资源目录结构可以看到有一个资源,是以id导出;
分析资源目录项结构:
00000804 ->最高位为0说明图标以id导出,且id为804;
000001b8 ->最高位为0说明是第三层,图标的IMAGE_RESOURCE_DATA_ENTRY地址 = 29000 + 1b8 = 291b8;
5)分析资源数据信息
找到291b8处:
对比IMAGE_RESOURCE_DATA_ENTRY结构分析:
图标的RVA = 02b448;
图标的大小 = 368;
将RVA转成FOA:
2b448在.rsrc节中;
foa = 2b448 - 2b000 + 29000 = 29448;
图标结束地址 = 29448 + 368 = 297b0;
也就是说:
29448~297cf为第一个图标的二进制数据形式;
6)ICO头
上面得到的二进制数据只是图标.ico文件的一部分数据,并不能直接重命名为.ico文件来当图标使用;
一个.ico图标文件可能由多个图标组成,因为同一个图标需要有不同的大小来适应不同分辨率的环境;
上面得到的只是.ico文件其中一个图标的部分数据,还缺少ICO头信息;
如图:完整的.ico文件
ICO分为两部分:
图标头 ->每个.ico文件有一个,用来描述ico文件由多少个图标构成等
图标项 ->.ico文件中有几个图标就有几个图标项,用来描述每个图标的大小、尺寸等信息;
例如:一个图标的ICO头解析
7)图标组
pe文件的ICO头信息在图标组中;
想提取程序的图标就需要找到图标组;
图标组和.ico文件中数据的区别:
1】图标组中
2】.ico文件中
3】区别:
图标组和.ico文件中的数据只有每一个图标项的最后一个字段有区别;
图标组中为2个字节,表示pe文件中图标的编号;
.ico文件中有4个字节,表示图标数据在.ico文件中的偏移;
4】总结:
从pe文件中提取图标时,需要将图标组中的每一个图标项的最后一个字段修改;
将图标的id改为图标数据在.ico文件中的偏移;
分析第一层
图标组也是一种预定义资源类型,id为E;
找到资源目录,29000处,往下面找到图标组,也就是id为5的资源目录项;
图标组地址 = 29000 + 90 = 29090;
分析第二层
到29090处:
有2个图标组;(程序的大图标和小图标各一个图标组)
图标组以id导出,id分别为65、68;
只分析图标组1:
图标组1的下一层地址 = 29000 + 188 = 29188;
分析第三层:
到29188处:
有1个资源数据结构;
以id导出,id=804;
图标组的资源数据结构地址 = 29000 +248 =29248;
分析图标组的资源数据:
到29248处:
图标组RVA = 52180
图标组大小 = 68个字节;
FOA = 52180-2b000+29000 = 50180;
也就是说:50180~501e7为第一个图标组的数据
分析:
1】图标头:
0001 ->表示资源类别为.ico;
0007 ->表示该ico由7个图标组成;
2】第一个图标项
10 ->宽16
10 ->高16
00 ->颜色数
00 ->保留
0001 ->调色板
0018 ->RGB每字节的位
00000368 ->图标大小368
0001 ->图标的编号01
8)提取图标
为了简单只提取第一个图标;
提取图标组:
将图标组1的数据复制到文件中;
修改图标头的图标数为1,也就是把7该为1;
然后添加2个字节,将最后一项改为图标数据的偏移;
将第一个图标的数据添加到后面:
然后重命名文件,加上后缀名.ico;
可以看到成功提取了图标,只不过是16x16的尺寸有点模糊;