程序的资源文件

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的尺寸有点模糊;
 
 
 
 

猜你喜欢

转载自www.cnblogs.com/ShiningArmor/p/12077695.html