因为某些原因,我想将一个EXE中的程序图标换成另外一个EXE文件中的图标。
这里说的图标并不是指EXE中所有的图标。EXE文件中可能包含成百上千的图标资源,我的更换目标只是程序图标,就是在windows资源管理器中所看到的图标。
讲到图标,先讲讲这两天我在这方面的收获。
先说下ICO,图标格式文件。
typedef struct
{
WORD idReserved; // Reserved (must be 0)
WORD idType; // Resource Type (1 for icons)
WORD idCount; // How many images?
ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
} ICONDIR, *LPICONDIR;
typedef struct
{
BYTE bWidth; // Width, in pixels, of the image
BYTE bHeight; // Height, in pixels, of the image
BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
BYTE bReserved; // Reserved ( must be 0)
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;
ICONDIR是ICO文件头,ICONDIRENTRY是单个图像的描述。
一个ICONDIRENTRY是16个字节,一个ICONDIR是22个字节。
用十六进制编辑器打开一个标准的ICO文件,前面16个字节的数据就是ICONDIR。
一个文件只有一个ICONDIR,它描述了这个文件有几个图像或资源。
ICONDIRENTRY用于描述每个资源(图像)的属性和数据偏移,通过数据偏移就能找到实际的资源(图像)数据,至于这个数据最后要怎样显示出来,那是另外一个话题了。
ICONDIR的idCount属性描述了有几个资源,idEntries是第一个ICONDIRENTRY(资源属性)。如果idCount大于1,则剩下的ICONDIRENTRY,则紧跟在ICONDIR,16个字节之后。
ICO文件中除了开头的ICONDIR文件头和紧跟其后的ICONDIRENTRY,剩下的就是资源的实际数据了。
之前一直以为ICO跟JPG,BMP一样,一个文件里面只一幅图像,而实际是一个ICO文件里可能有若干个文件。
还有,为什么很多开发工具为应用程序指定图标时,要选择ICO文件?原因跟ICO这个特性有关。
比如说,你在资源管理器里面可以以多种查看方式浏览文件,如缩略图、平铺、图标、列表、详细信息方式。
其中差别最大的时图标与列表方式,所展现的文件图标的大小差异很大,Windows在不同的查看方式下会寻找你程序中最适合的图标来显示。
当Windows需要显示程序的图标时,下面三个因素会影响Windows查找最适合显示的图标:
1.查看方式(缩略图、平铺、图标、列表、详细信息方式等)
2.当前显示器的显示颜色质量(256色,16位真彩,32真彩等)。
3.当前系统的语言(英文,简体中文,繁体中文等)
一般而言,你要为程序设计这几种类型的图标
16X16 256色,
32X32 256色,
48X48 256色,
16X16 16位真彩色,
32X32 16位真彩色,
48X48 16位真彩色,
Winddows 98/2000不支持16位真彩的图标,xp以上操作系统还支持32位真彩色的图标,但是个人认为16位真彩色与32位真彩色肉眼看不出差别,可以不设计此类图标。还有其它规格的图标,像24X24大小的,但是不常用。
这些知识帮我解决了心中一个疑惑:我之前用Delphi为程序指定图标时,经常是拿BMP图像去转成ICO用的,变成ICO里面只有一张图片,这样就不能适应资源管理器的多种查看方式,显示效果很差。
以前的做法是选些简单的图标,这样放大缩小后效果就不致于太糟。为了解决这个问题还专门去google过,看过有人拿VC的资源编辑器再建个资源文件给delphi用,还附有些副作用。
现在好了,知道原理后,以后为程序制作个“完整的”ICO文件就可以了。
photoshop不支持制作ICO,这里推荐个工具Axialis IconWorkshop,很好用。
讲了这么多,回到正题,怎样替换一个EXE中的程序图标呢?
从前从前,我写过篇通过解析PE格式来读出图标的文章,这种方式很麻烦,偶尔发现有人介绍了另外的方法,觉得难度要小不少。
可是在实际的编写代码的过程中,发现网上的文章的代码并不能直接使用,起码在我的电脑上不能。经过几番折腾,终于弄懂了怎么回事。
假设一下你想把1.exe中的程序图标换成2.exe中的图标。
首先,要更换一个程序中的图标要用到这几个API:
HANDLE BeginUpdateResource(
LPCTSTR pFileName, // pointer to executable file name
BOOL bDeleteExistingResources // deletion option
);
BOOL UpdateResource(
HANDLE hUpdate, // update-file handle
LPCTSTR lpType, // address of resource type to update
LPCTSTR lpName, // address of resource name to update
WORD wLanguage, // language identifier of resource
LPVOID lpData, // address of resource data
DWORD cbData // length of resource data, in bytes
);
BOOL EndUpdateResource(
HANDLE hUpdate, // update-file handle
BOOL fDiscard // write flag
);
BeginUpdateResource,EndUpdateResource比较好说,关键是UpdateResource,
BOOL UpdateResource(
HANDLE hUpdate,
// update-file handle,其实就是调用BeginUpdateResource的返回值
LPCTSTR lpType,
// address of resource type to update,要更新的资源的类型,它其实是个字符器/整数双用型的参数,
//这里使用预定义的RT_ICON,其它类型参考MSDN。
LPCTSTR lpName,
// address of resource name to update,要更新的资源的地址,也是字符器/整数双用型参数。
//当它的高字为零时,当整数使用;否则为字符串。大多数情况都是传整数参数,即图标编号。
WORD wLanguage,
// language identifier of resource,要更新的资源的语言标识,也只是说Windows可以在不同语言平台为一个程序显示不同的图标。
//这里使用了MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED),这样的缺点就是只支持简体中文版操作系统了。
LPVOID lpData,
// address of resource data,指定要用什么数据去更新资源,这是个数据指针
DWORD cbData
// length of resource data, in bytes,所要更新的数据的大小
);
那这个函数就有两个问题待解决
1.如何获取资源的地址(图标编号)
2.如何获取要更新的数据及大小
第一个问题
因为程序里面也许会有好多的图标,只有其中几个才是程序图标,那怎么找到程序图标呢?
我的做法是这样的,因为程序图标总是从编号1开始,所以我先使用EnumResourceNames遍历一下里面的图标资源,目标是找到前10个图标,如果没有10个就返回实际数。
要使用两次EnumResourceNames,分别对1.exe和2.exe中的图标进行遍历。
使用EnumResourceNames要指定回调函数,在回调函数中可以得到本次遍历的图标编号。
遍历图标得到图标编号后,根据编号分别打开两个EXE中的图标资源,得到它们的大小,因为大家都是编号前10的图标,再对比一下它们彼此之前的大小,如果一样则进行替换。
那紧跟着就是第二个问题了,怎样根据编号获取到对应的数据呢?
HINSTANCE hInsSrc = LoadLibrary("文件名");
HRSRC hResInfo = ::FindResource(hInsSrc, MAKEINTRESOURCE(图标编号), RT_ICON);
HGLOBAL hGlobal = ::LoadResource(hInsSrc, hResInfo1);
DWORD dwSize = ::SizeofResource(hInsSrc, hResInfo1);
void* pData = ::LockResource(hGlobal2);//取得数据,系统自动释放资源。
这样,dwSize就是数据大小,pData就是数据指针了。
至此,更新资源最大的两个问题已经解决,但还有个很“奇怪”的问题,一开始调用UpdateResource,明明返回成功,但系统不会更换1.exe中的图标,反而在同目录下生成一个***.tmp的文件(***表示随机),把***.tmp改名后就是被修改后的1.exe了。最后发现,是因为我在UpdateResource之前对EXE进行遍历,获取资源操作时,LoadLibrary装载了文件。在UpdateResource之前就要关闭文件FreeLibrary才行。
但是UpdateResource又返回成功。。。。唉。
下面贴代码了, 代码并不完善。vc6.0 + windows2003测试通过。欢迎指点我。
//
static WORD g_wSrcIconCount1 = 0;
static WORD g_aIconID1[5] = {1, 2, 3, 4, 5};
static WORD g_wSrcIconCount2 = 0;
static WORD g_aIconID2[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
BOOL CALLBACK EnumResNameProc1(HINSTANCE hModule, LPCTSTR lpszType, LPTSTR lpszName, LONG lParam)
{
if(((DWORD)lpszName & 0xffff0000) == 0)
{
WORD wID = (WORD)lpszName;
if(wID >= 1 && wID <= 5)
{
g_aIconID1[g_wSrcIconCount1] = wID;
g_wSrcIconCount1 += 1;
}
}
return TRUE;
}
BOOL CALLBACK EnumResNameProc2(HINSTANCE hModule, LPCTSTR lpszType, LPTSTR lpszName, LONG lParam)
{
if(((DWORD)lpszName & 0xffff0000) == 0)
{
WORD wID = (WORD)lpszName;
if(wID >= 1 && wID <= 10)
{
g_aIconID2[g_wSrcIconCount2] = wID;
g_wSrcIconCount2 += 1;
}
}
return TRUE;
}
int ReleaseExeIcon(char* szSrcFile, char* szDstFile)
{
int nReplace = 0;
HINSTANCE hInsSrc = LoadLibrary(szSrcFile);
HINSTANCE hInsDst = LoadLibrary(szDstFile);
if(hInsSrc == NULL || hInsDst == NULL)
{
if(hInsSrc != NULL) FreeLibrary(hInsSrc);
if(hInsDst != NULL) FreeLibrary(hInsDst);
return 0;
}
g_wSrcIconCount1 = 0;
g_wSrcIconCount2 = 0;
EnumResourceNames(hInsSrc, RT_ICON, EnumResNameProc1, 0);
EnumResourceNames(hInsDst, RT_ICON, EnumResNameProc2, 0);
for(int i = 0; i < g_wSrcIconCount1; i++)
{
HRSRC hResInfo1 = ::FindResource(hInsSrc, MAKEINTRESOURCE(g_aIconID1[i]), RT_ICON);
HGLOBAL hGlobal1 = ::LoadResource(hInsSrc, hResInfo1);
DWORD dwSize1 = ::SizeofResource(hInsSrc, hResInfo1);
//在目标文件中找到合适大小的图标
for(int j = 0; j < g_wSrcIconCount2; j++)
{
HRSRC hResInfo2 = ::FindResource(hInsDst, MAKEINTRESOURCE(g_aIconID2[j]), RT_ICON);
HGLOBAL hGlobal2 = ::LoadResource(hInsDst, hResInfo2);
DWORD dwSize2 = ::SizeofResource(hInsDst, hResInfo2);
//如果大小一致才复制替换图标内容
if(dwSize1 == dwSize2)
{
//更新图标前必须关闭文件
FreeLibrary(hInsSrc);
//更新图标
HANDLE hUpdate = ::BeginUpdateResource(szSrcFile, FALSE);
void* pData = ::LockResource(hGlobal2);//取得数据,系统自动释放资源。
BOOL bSuccess = ::UpdateResource(hUpdate, RT_ICON, MAKEINTRESOURCE(g_aIconID1[i]),
MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED),
pData, dwSize2);
::EndUpdateResource(hUpdate, FALSE);
//更新完图标后自动打开文件
hInsSrc = LoadLibrary(szSrcFile);
/
if(bSuccess)
nReplace += 1;
break;
}
}
}
FreeLibrary(hInsSrc);
FreeLibrary(hInsDst);
return nReplace;
}
原创,温校宏.2010/02/02