65.9K
CodeProject 正在变化。 阅读更多。
Home

替换 EXE 和 DLL 文件中的图标资源

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (18投票s)

2008年11月3日

GPL3

6分钟阅读

viewsIcon

101387

downloadIcon

2814

一个简单的例子,说明如何读取 *.ico 文件以及如何替换图标资源。

引言

本文旨在解释与 ICO 文件和图标资源相关的结构。代码示例将演示这些结构的使用以及如何将这些结构嵌入到 DLL 文件中。

背景

我开始开发自己的资源破解器,因为我需要自动化 DLL 文件中的字符串和图标替换。我开始在 MSDN 上阅读有关使用库以及处理资源的基本函数的文章。

我了解到枚举资源和打开资源并不困难,但当你真正想对该资源做些什么,例如修改或替换它时,问题就出现了。

最有用的文章是:《二进制资源格式》。在这里,我找到了关于处理 OpenResource 函数返回的字节数组所需的大部分信息。

要了解我使用的不同函数的工作原理,您应该查看 MSDN:《资源参考》。

使用代码

我将首先解释与 ICO 文件和图标资源相关的结构。ICO 文件是一个按照一些简单规则排列的字节数组。ICO 文件由一个放置在文件开头的 ICONDIR 结构组成。

typedef struct 
{
    WORD            idReserved;   // Reserved
    WORD            idType;       // resource type (1 for icons)
    WORD            idCount;      // how many images?
    LPICONDIRENTRY    idEntries; // the entries for each image
} ICONDIR, *LPICONDIR;
  • idReserved 始终为 0。
  • idType 对于图标文件为 1。
  • idCount 是 ICO 文件包含的图像数量。
  • idEntries 是一个指向 idCount 结构的指针,这些结构描述了文件中的图像(这些结构紧跟在 ICONDIR 之后写入文件)。这些结构的类型是 ICONDIRENTRY
typedef struct
{
    BYTE    bWidth;               // Width of the image
    BYTE    bHeight;              // Height of the image (times 2)
    BYTE    bColorCount;          // Number of colors in image (0 if >=8bpp)
    BYTE    bReserved;            // Reserved
    WORD    wPlanes;              // Color Planes
    WORD    wBitCount;            // Bits per pixel
    DWORD    dwBytesInRes;         // how many bytes in this resource?
    DWORD    dwImageOffset;        // where in the file is this image
} ICONDIRENTRY, *LPICONDIRENTRY;
  • dwBytesInRes 表示图像的总大小,即表示实际图像的 ICONIMAGE 结构的总大小。
  • dwImageOffset 表示 ICO 文件中可以找到该图像的 ICONIMAGE 结构的偏移量。

我还没有真正尝试通过修改 ICONIMAGE 字段来改变图像的外观,所以我只能说图像具有以下结构

BITMAPINFOHEADER icHeader;
RGBQUAD icColors[1];
BYTE icXOR[1];
BYTE icAND[1];

并且 BITMAPINFOHEADER 具有以下字段,但只使用了 SizeWidthHeightPlanesBitCountSizeImage。其余为 0

typedef struct tagBITMAPINFOHEADER{
  DWORD  biSize; 
  LONG   biWidth; 
  LONG   biHeight; 
  WORD   biPlanes; 
  WORD   biBitCount; 
  DWORD  biCompression; 
  DWORD  biSizeImage; 
  LONG   biXPelsPerMeter; 
  LONG   biYPelsPerMeter; 
  DWORD  biClrUsed; 
  DWORD  biClrImportant; 
} BITMAPINFOHEADER, *PBITMAPINFOHEADER; 

在我的程序中,我定义了以下结构

typedef struct
{
    UINT            Width, Height, Colors; // Width, Height and bpp
    LPBYTE            lpBits;                // ptr to DIB bits
    DWORD            dwNumBytes;            // how many bytes?
    LPBITMAPINFO    lpbi;                  // ptr to header
    LPBYTE            lpXOR;                 // ptr to XOR image bits
    LPBYTE            lpAND;                 // ptr to AND image bits
} ICONIMAGE, *LPICONIMAGE; 

要了解更多关于如何使用此结构的信息,您应该查看此源代码:《ICONS.c》。

那么现在,让我们总结一下如何读取 ICO 文件

首先,读取 ICONDIR 结构

ReadFile( hFile1, &(pIconDir->idReserved), sizeof( WORD ), &dwBytesRead, NULL );
ReadFile( hFile1, &(pIconDir->idType), sizeof( WORD ), &dwBytesRead, NULL );
ReadFile( hFile1, &(pIconDir->idCount), sizeof( WORD ), &dwBytesRead, NULL );

接下来,读取 idCountICONDIRENTRY 结构

for(i=0;i<pIconDir->idCount;i++)
{
  ReadFile( hFile1, &pIconDir->idEntries[i], sizeof(ICONDIRENTRY), 
                    &dwBytesRead, NULL );
}

现在,对于每个 ICONDIRENTRY,找到 ICO 文件中表示实际图像(漂亮的彩色像素)的关联 ICONIMAGE 结构,并读取它。

for(i=0;i<pIconDir->idCount;i++)
{
  pIconImage = (LPICONIMAGE)malloc( pIconDir->idEntries[i].dwBytesInRes );
  SetFilePointer( hFile1, pIconDir->idEntries[i].dwImageOffset,NULL, FILE_BEGIN );
  ReadFile( hFile1, pIconImage, pIconDir->idEntries[i].dwBytesInRes,
                        &dwBytesRead, NULL);
  arrayIconImage[i]=(LPICONIMAGE)malloc( pIconDir->idEntries[i].dwBytesInRes );
  memcpy( arrayIconImage[i],pIconImage, pIconDir->idEntries[i].dwBytesInRes );
  free(pIconImage);
}

现在,您拥有了表示 ICO 文件的所有结构。

DLL 文件中使用的结构与我上面描述的几乎相同。所以,既然您知道 ICO 文件的结构,修改 DLL 中的图标资源将非常容易。

让我们看看我用于更新 DLL 的结构。我只会指出与上述结构之间的区别

typedef struct 
{
    WORD            idReserved;   // Reserved
    WORD            idType;       // resource type (1 for icons)
    WORD            idCount;      // how many images?
    LPMEMICONDIRENTRY    idEntries; // the entries for each image
} MEMICONDIR, *LPMEMICONDIR;

MEMICONDIRICONDIR 相同。

typedef struct
{
    BYTE    bWidth;               // Width of the image
    BYTE    bHeight;              // Height of the image (times 2)
    BYTE    bColorCount;          // Number of colors in image (0 if >=8bpp)
    BYTE    bReserved;            // Reserved
    WORD    wPlanes;              // Color Planes
    WORD    wBitCount;            // Bits per pixel
    DWORD    dwBytesInRes;         // how many bytes in this resource?
    WORD    nID;                  // the ID
} MEMICONDIRENTRY, *LPMEMICONDIRENTRY;

正如您所看到的,对于资源条目,我们不再需要 dwImageOffset 字段。原因是我们可以使用捆绑 ID(nID)在 DLL 中查找或更新图像资源。FindResourceUpdateResource 需要知道资源的类型(lbType)、语言(langId)和捆绑 ID(lpName)。

ICO 资源在 DLL 中的保存方式如下

RT_GROUP_ICON 资源包含 MEMICONDIRMEMICONDIRENTRY 结构。RT_GROUP_ICON 是 VS 中的资源编辑器用来显示图标的内容。如果这些资源不存在,您就无法在资源编辑器中看到图标资源。

对于每个 MEMICONDIRENTRY,都有一个 RT_ICON 资源,其捆绑 ID 由 MEMICONDIRENTRY 结构中的 nID 字段标识。

现在,我们可以使用在 ICO 文件中找到的结构来替换 DLL 中的图标资源(我们知道要替换的资源的捆绑 ID(lpName)、语言(langID)和类型 RT_GROUP_ICON)。

hUi = LoadLibraryExA(lpFileName,NULL,
                     DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);
HRSRC hRsrc = FindResourceEx(hUi, RT_GROUP_ICON, lpName,langId);

HGLOBAL hGlobal = LoadResource( hUi, hRsrc );
    test1 =(BYTE*) LockResource( hGlobal );

首先,我们使用 test1 读取 DLL 中已存在的资源。我们构建 lpGrpIconDir 以保存最初使用的 RT_ICON 捆绑 ID。

LPMEMICONDIR lpGrpIconDir=new MEMICONDIR;
lpGrpIconDir->idReserved=pIconDir->idReserved;
lpGrpIconDir->idType=pIconDir->idType;
lpGrpIconDir->idCount=pIconDir->idCount;
cbRes=3*sizeof(WORD)+lpGrpIconDir->idCount*sizeof(MEMICONDIRENTRY);
test=new BYTE[cbRes];
temp=test;
CopyMemory(test,&lpGrpIconDir->idReserved,sizeof(WORD));
test=test+sizeof(WORD);
CopyMemory(test,&lpGrpIconDir->idType,sizeof(WORD));
test=test+sizeof(WORD);
CopyMemory(test,&lpGrpIconDir->idCount,sizeof(WORD));
test=test+sizeof(WORD);

lpGrpIconDir->idEntries=new MEMICONDIRENTRY[lpGrpIconDir->idCount];
for(i=0;i<lpGrpIconDir->idCount;i++)
{
    lpGrpIconDir->idEntries[i].bWidth=pIconDir->idEntries[i].bWidth;
    CopyMemory(test,&lpGrpIconDir->idEntries[i].bWidth,sizeof(BYTE));
    test=test+sizeof(BYTE);
    lpGrpIconDir->idEntries[i].bHeight=pIconDir->idEntries[i].bHeight;
    CopyMemory(test,&lpGrpIconDir->idEntries[i].bHeight,sizeof(BYTE));
    test=test+sizeof(BYTE);
    lpGrpIconDir->idEntries[i].bColorCount=pIconDir->idEntries[i].bColorCount;
    CopyMemory(test,&lpGrpIconDir->idEntries[i].bColorCount,sizeof(BYTE));
    test=test+sizeof(BYTE);
    lpGrpIconDir->idEntries[i].bReserved=pIconDir->idEntries[i].bReserved;
    CopyMemory(test,&lpGrpIconDir->idEntries[i].bReserved,sizeof(BYTE));
    test=test+sizeof(BYTE);
    lpGrpIconDir->idEntries[i].wPlanes=pIconDir->idEntries[i].wPlanes;
    CopyMemory(test,&lpGrpIconDir->idEntries[i].wPlanes,sizeof(WORD));
    test=test+sizeof(WORD);
    lpGrpIconDir->idEntries[i].wBitCount=pIconDir->idEntries[i].wBitCount;
    CopyMemory(test,&lpGrpIconDir->idEntries[i].wBitCount,sizeof(WORD));
    test=test+sizeof(WORD);
    lpGrpIconDir->idEntries[i].dwBytesInRes=pIconDir->idEntries[i].dwBytesInRes;
    CopyMemory(test,&lpGrpIconDir->idEntries[i].dwBytesInRes,sizeof(DWORD));
    test=test+sizeof(DWORD);
    if(i<lpInitGrpIconDir->idCount) //nu am depasit numarul initial de RT_ICON
    lpGrpIconDir->idEntries[i].nID=lpInitGrpIconDir->idEntries[i].nID;
    else
    {
       nMaxID++;
       lpGrpIconDir->idEntries[i].nID=nMaxID; 
    }
       CopyMemory(test,&lpGrpIconDir->idEntries[i].nID,sizeof(WORD));
    test=test+sizeof(WORD);
}

我们构建了更新资源所需的字节数组。UpdateResource 函数必须接收一个指向数组的指针,该数组的构建方式与上述描述完全相同。如果新的图标资源包含的图像比初始资源多,我们将分配新的捆绑 ID。如果它包含的图像少,我们将删除不再使用的 RT_ICON 资源。

hUpdate = BeginUpdateResourceA(lpFileName, FALSE);

res=UpdateResource(hUpdate,RT_GROUP_ICON,lpName,langId,temp,cbRes);

for(i=0;i<lpGrpIconDir->idCount;i++)
{
    res=UpdateResource(hUpdate,RT_ICON,MAKEINTRESOURCE(lpGrpIconDir->idEntries[i].nID),
                       langId,pIconImage[i], lpGrpIconDir->idEntries[i].dwBytesInRes);
}

如果存在未被更新的 RT_GROUP_ICON 引用的 RT_ICON 资源,我们将删除它们

for(i=lpGrpIconDir->idCount;i<lpInitGrpIconDir->idCount;i++)
{
  res=UpdateResource(hUpdate,RT_ICON,
                     MAKEINTRESOURCE(lpInitGrpIconDir->idEntries[i].nID),langId,"",0);
}

以下是一些我们必须在 MSDN 上阅读的函数以及我在程序中使用的函数

LoadLibraryExA(lpSrcFileName,NULL,
               DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);

这用于获取 DLL 文件的句柄。(我的示例仅当 DLL 未被 VS 中的资源编辑器打开时才有效。)

HRSRC hRsrc = FindResourceEx(hUi, RT_GROUP_ICON, lpName,langId);
HGLOBAL hGlobal = LoadResource( hUi, hRsrc );
LockResource( hGlobal );

这些用于将特定资源加载到内存中,作为指向字节数组的指针。

hUpdate = BeginUpdateResourceA(lpFileName, FALSE);
res=UpdateResource(hUpdate,RT_GROUP_ICON,lpName,langId,temp,cbRes);
EndUpdateResource(hUpdate,FALSE);

您需要这些函数来更新特定资源。请记住,UpdateResources 必须提供一个字节数组,其格式如《二进制资源格式》一文中所述。

EnumResourceTypes(hui,(ENUMRESTYPEPROC)EnumTypesFunc,0) 

此函数枚举 DLL 中的所有资源类型。EnumTypesFunc 是我的回调函数,它获取实际的资源类型并调用 EnumNamesFunc()

EnumResourceNames((HINSTANCE)hModule,lpType,(ENUMRESNAMEPROC)EnumNamesFunc,0)

此函数枚举所有资源捆绑名称。我的回调函数是

BOOL EnumNamesFunc(HANDLE hModule, LPCTSTR lpType, LPTSTR lpName, LONG lParam)

回调函数将接收资源的类型和名称。名称表示捆绑 ID。每个资源类型可以有一个或多个由唯一捆绑 ID 标识的资源。

EnumResourceLanguages((HMODULE)hModule,lpType,lpName,
            (ENUMRESLANGPROC)EnumLangsFunc,0); 

此函数枚举由其类型(lpType)和捆绑 ID(lpName)标识的资源可用的所有语言

LPICONIMAGE* ExtractIcoFromFile(LPSTR filename,LPICONDIR pIconDir) 

此函数打开一个 *.ico 文件并读取 ICONDIRICONIMAGE 结构中的数据

BOOL ReplaceIconResource(LPSTR lpFileName, LPCTSTR lpName,  
                         UINT langId, LPICONDIR pIconDir,LPICONIMAGE* pIconImage) 

此函数使用 ICONDIRICONIMAGE 结构替换由捆绑 ID(lpName)和语言 ID(langId)标识的图标资源。

用法

我给出的示例程序必须接收以下参数:要替换的 DLL 文件中 RT_GROUP_ICON(对于 test.dll 尝试 105)资源的 ID,以及 ICO 文件的名称(如果文件不在您的项目文件夹中,请记住给出正确的路径)。

其他项目

我正在从事的另一个项目包括一个关于 Web 界面的博客和网站。目前,文章只提供罗马尼亚语版本,但很快将有更多关于 Web 技术和 Web 界面设计的文章和教程,也将提供英语版本。

请访问我的博客:Interfete Web

© . All rights reserved.