使用VS2013实现修改其他程序的图标

YouthShouting

发布日期: 2018-12-20 12:17:38 浏览量: 1660
评分:
star star star star star star star star star_border star_border
*转载请注明来自write-bug.com

背景

之前,写了一个程序,程序中有一个功能就是获取一个EXE程序的图标,然后,把这个图标更改为另一个EXE程序的图标。在网上搜索了很久,都没有找到相关的例子。找到的大都是修改自己程序的图标,或者是使用一个 .ico 的图标文件,去更改指定程序的图标。虽然,这些功能和自己想要开发的功能有些不同,但,也还是具有一定的参考价值的。

现在,我就对修改指定程序图标的方式进行总结。总结出 3 种修改方式:

  • 替换的图标存储在自己程序的资源中

  • 替换的图标以 .ico 图标文件形式提供

  • 替换的图标是其他一个EXE程序的图标

现把程序实现的原理以及实现的过程,写成文档分享给大家,方便大家的参考。

函数介绍

FindResource介绍

函数声明

  1. HRSRC FindResource(
  2. HMODULE hModule,
  3. LPCWSTR lpName,
  4. LPCWSTR lpType );

参数

  • hModule:处理包含资源的可执行文件的模块。NULL值则指定模块句柄指向操作系统通常情况下创建最近过程的相关位图文件。
  • lpName:指定资源名称。若想了解更多的信息,请参见注意部分。
  • lpType:指定资源类型。若想了解更多的信息,请参见注意部分。作为标准资源类型。这个参数的含义同EnumResLangProc\lpType。

返回值

  • 如果函数运行成功,那么返回值为指向被指定资源信息块的句柄。为了获得这些资源,将这个句柄传递给LoadResource函数。如果函数运行失败,则返回值为NULL。若想获得更多错误信息,请调用GetLastError函数。

SizeofResource 介绍

函数声明

  1. DWORD SizeofResource(
  2. HMODULE hModule, // module handle
  3. HRSRC hResInfo // resource handle
  4. );

参数

  • hModule:包合资源的可执行文件模块的句柄。
  • hReslnfo:资源句柄。此句柄必须由函数FindResource或FindResourceEx来创建。

返回值

  • 如果函数运行成功,返回值资源的字节数。如果函数运行失败,返回值为零。若想获得更多的错误信息,请调用GetLastError函数。

LoadResource 介绍

函数声明

  1. HGLOBAL LoadResource(
  2. HMODULE hModule, // module handle
  3. HRSRC hResInfo // resource handle
  4. );

参数

  • hModule:处理包合资源的可执行文件的模块句柄。若hModule为NULL,系统从当前过程中的模块中装载资源。
  • hReslnfo:将被装载资源的句柄。它必须由函数FindResource或FindResourceEx创建。

返回值

  • 如果函数运行成功,返回值是相关资源的数据的句柄。如果函数运行失败,返回值为NULL。若想获得更多的错误信息,请调用GetLastError函数。

LockResource 介绍

函数声明

  1. LPVOID LockResource(
  2. HGLOBAL hResData // handle to resource
  3. );

参数

  • hResDate:被装载的资源的句柄。函数LoadResource可以返回这个句柄。

返回值

  • 如果被装载的资源被锁住了,返回值是资源第一个字节的指针;否则为NULL。

BeginUpdateResource 介绍

该函数返回一个可被UpdateResource函数使用的句柄以便在一个可执行文件中增加、删除或替换资源。

函数声明

  1. HANDLE BeginUpdateResource(
  2. LPCTSTR pFileName, // executable file name
  3. BOOL bDeleteExistingResources // deletion option
  4. );

参数

  • pFileName:指向一个表示结束的空字符串指针,它是用来指定用以更新资源的基于32-位可执行文件的文件名。应用程序必须获得访问这个文件的可写权限,并且此文件在当前状态下不能被执行。如果pFileName未被指定完全路径,系统将在当前路径下搜寻此文件。
  • bDeleteExistingResources:说明是否删除pFileName参数指定的现有资源。如果这个参数为TRUE则现有的资源将被删除,而更新可执行文件只包括由UpdateResource函数增加的资源。如果这个参数为FALSE,则更新的可执行文件包括现有的全部资源,除非通过UpdateResource特别说明被删除或是替换的。

返回值

  • 如果此函数运行成功,其值将通过使用UpdateResource和EndUpdateResource函数返回一个句柄。如果被指定的文件不是一个可执行文件,或者可执行文件已被装载,或者文件不存在,或是文件不能被打开写入时,则返回值为空。若想获得更多的错误信息,请调用GetLastError函数。

UpdateResource 介绍

增加 删除 或替文件中的资源。

函数声明

  1. BOOL UpdateResource(
  2. HANDLE hUpdate, // update-file handle
  3. LPCTSTR lpType, // resource type
  4. LPCTSTR lpName, // resource name
  5. WORD wLanguage, // language identifier
  6. LPVOID lpData, // resource data
  7. DWORD cbData // length of resource data
  8. );

参数

  • hUpdate:指定更新文件句柄。此句柄由BeginUpdateResource函数返回。
  • lpType:指向说明将被更新的资源类型的字符串,它以NULL为终止符。这个参数可以是一个通过宏MAKENTRESOURCE传递的整数值,含义参见EnumResLangProc\lpType。
  • lpName:指向说明待被更新的资源名称的字符串,它以NULL为终止符。这个参数可以是一个通过宏MAKEINTRESOURCE传递的整数值。
  • wLanguage:指定将被更新资源的语言标识。要了解基本的语言标识符以及由这些标识符组成的字语言标识符的列表,可参见宏MAKELANGID。
  • lpData:指向被插入可执行文件的资源数据的指针。如果资源是预定义类型值之一,那么数据必须是有效且适当排列的。注意这是存储在可执行文件中原始的一进制数据,而不是由Loadlcon,LoadString或其他装载特殊资源函数提供的数据。所有包含字符串、文本的数据必须是Unicode格式;IpData不能指向ANSI数据。如果lpData为NULL,所指定的资源将从可执行文件中被删除。
  • cbData:指定lpData中的资源数据数据大小,以字节计数。

返回值

  • 如果函数运行成功,返回值为非零;如果函数运行失败,返回值为零。若想获得更多的错误信息,请调用GetLastError函数。

EndUpdateResource 介绍

终止在可执行文件中的资源更新。

函数声明

  1. BOOL EndUpdateResource(
  2. HANDLE hUpdate, // update-file handle
  3. BOOL fDiscard // write option
  4. );

参数

  • hUpdate:用于资源更新的句柄。此句柄通过BeginUpdateResource函数返回。
  • fDiscard:用来说明是否向可执行文件中写入资源更新内容。如果此参数为TRUE,则在可执行文件中无变化;如果此参数为FALSE,则在可执行文件中写入变化。

返回值

  • 如果函数运行成功,并且通过调用UpdateResource函数指定的不断积聚的资源修正内容被写入指定的可执行文件,那么其返回值为非零。如果函数运行失败,其返回值为零。若想获得更多的错误信息,请调用GetLastError函数。

程序实现原理

有一个知识点大家需要了解的就是:

EXE程序会默认把资源ID号最小的ICON类型的图标资源作为自己的程序图标。

也就是说,要想更改EXE程序的图标,只需要把一个图标创建或替换程序中ICON类型的资源ID最小的图标就可以了。这样,程序就会默认把该图标资源作为自己的程序图标,显示出来。

那么,图标修改的大致原理就是:

  • 获取替换图标的图标数据的存储地址

  • 使用 UpdateResource 函数定位出程序 RT_ICON 类型、资源ID为 1 的图标资源,并使用上述的替换图标数据进行创建或者替换

这样,就成功替换或者创建了一个资源类型是 RT_ICON,而且资源ID为 1 的最小的图标资源。要注意的是,最小的资源ID号是以 1 开始计数,而不是以 0 开始的,这个需要注意。

那么,对于上面提到的 3 种方式,主要是获取图标数据的方式上的区别而已。

针对第 1 种方式:替换的图标存储在自己程序的资源中。获取图标数据的原理就是:

  • 首先,我们先定位到我们程序里的资源,主要是根据“资源类型”和“资源名称”进行定位,获取资源信息块的句柄

  • 然后,根据上面获取的资源信息块的句柄,把资源加载到我们程序的内存中

  • 最后,锁定加载到内存中的资源的内存,防止程序的其他操作影响到这块内存,获取图标数据的首地址

针对第 2 种方式:替换的图标以 .ico 图标文件形式提供。获取图标数据的原理就是:

  • 首先,以读方式打开文件,并获取文件的大小

  • 然后,将文件数据内容全部都出去出来,这样便获取到了图标数据

针对第 3 种方式:替换的图标是其他一个EXE程序的图标。获取图标数据的方法和第 1 种方式的方法类似,原理是:

  • 首先,我们先把想要提取图标的EXE将在到我们的程序中来,并获取加载句柄

  • 然后,根据程序加载句柄,定位到程序里的资源,主要是根据“资源类型”和“资源名称”进行定位,获取资源信息块的句柄

  • 接着,根据上面获取的资源信息块的句柄,把资源加载到我们程序的内存中

  • 最后,锁定加载到内存中的资源的内存,防止程序的其他操作影响到这块内存,获取图标数据的首地址

编码实现

在这 3 种方式的图标数据获取中,最麻烦,而且最能体现技术含量的就是第 3 中形式。所以,我们以第 3 种实现方式为例,写出实现的代码。对于其余两种,网上有很多相关的例子和参考资料,相信大家根据原理部分应该自己也可以独立写出来,在此就不给出代码了。

  1. BOOL ChangeIcon(char *pszChangedIconExeFileName, char *pszSrcIconExeFileName)
  2. {
  3. // 将在其他程序,并获取程序模块句柄
  4. HMODULE hEXE = ::LoadLibrary(pszSrcIconExeFileName);
  5. if (NULL == hEXE)
  6. {
  7. FreeRes_ShowError("LoadLibrary");
  8. return FALSE;
  9. }
  10. // 获取其他EXE程序图标资源数据
  11. HRSRC hRsrc = ::FindResource(hEXE, (LPCSTR)1, RT_ICON);
  12. if (NULL == hRsrc)
  13. {
  14. FreeRes_ShowError("FindResource");
  15. return FALSE;
  16. }
  17. // 获取资源大小
  18. DWORD dwSize = ::SizeofResource(hEXE, hRsrc);
  19. if (0 >= dwSize)
  20. {
  21. FreeRes_ShowError("SizeofResource");
  22. return FALSE;
  23. }
  24. // 加载资源到程序内存
  25. HGLOBAL hGlobal = ::LoadResource(hEXE, hRsrc);
  26. if (NULL == hGlobal)
  27. {
  28. FreeRes_ShowError("LoadResource");
  29. return FALSE;
  30. }
  31. // 锁定资源内存
  32. LPVOID lpVoid = ::LockResource(hGlobal);
  33. if (NULL == lpVoid)
  34. {
  35. FreeRes_ShowError("LockResource");
  36. return FALSE;
  37. }
  38. // 开始修改图标
  39. HANDLE hUpdate = ::BeginUpdateResource(pszChangedIconExeFileName, FALSE);
  40. if (NULL == hUpdate)
  41. {
  42. FreeRes_ShowError("BeginUpdateResource");
  43. return FALSE;
  44. }
  45. // 如果资源ID存在, 则替换资源; 否则创建资源
  46. // 程序把ICON的最小的资源ID作为程序图标, 所以从1开始, 1最小
  47. BOOL bRet = ::UpdateResource(hUpdate, RT_ICON, (LPCSTR)1, LANG_NEUTRAL, lpVoid, dwSize);
  48. if (FALSE == bRet)
  49. {
  50. FreeRes_ShowError("UpdateResource");
  51. return FALSE;
  52. }
  53. ::EndUpdateResource(hUpdate, FALSE);
  54. // 释放模块
  55. ::FreeLibrary(hEXE);
  56. return TRUE;
  57. }

程序测试

现在,我们在 main 函数中,调用上述封装好的函数进行测试,把 “520.exe” 程序的图标改成 “360.exe” 程序的图标。

main 函数为:

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. // 把 520.exe 的图标更改为 360.exe 的图标
  4. if (FALSE == ChangeIcon("C:\\Users\\DemonGan\\Desktop\\520.exe", "C:\\Users\\DemonGan\\Desktop\\360.exe"))
  5. {
  6. printf("Change Icon Error!\n");
  7. }
  8. else
  9. {
  10. printf("Change Icon OK!\n");
  11. }
  12. system("pause");
  13. return 0;
  14. }

在测试之前,“360.exe” 程序和 “520.exe” 程序图标如下图所示:

运行修改图标的程序,提示执行成功:

然后,再次查看 “360.exe” 程序和 “520.exe” 程序,发现 “520.exe” 图标成功更改了:

由此可见,程序测试成功。

总结

改程序的核心原理就是:EXE程序会默认把资源ID号最小的ICON类型的图标资源作为自己的程序图标。

其中,测试的时候,需要注意的一个问题就是,图标更改成功后,可能会看到图标仍然没有更改。那可能是因为系统缓存了原来程序的图标,并没有更新图标缓存的缘故。最简单的方法就是,把修改图标后的程序,拷贝到其他目录下进行查看,这样操作比较简便些。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download ChangeIcon_Test.7z ( 154.06kb, 12次下载 )

keyboard_arrow_left上一篇 : 基于Python使用词云图 C语言-基于Huffman编码原理的译码解压缩程序 : 下一篇keyboard_arrow_right



YouthShouting
2018-11-19 09:22:31
EXE程序会默认把资源ID号最小的ICON类型的图标资源作为自己的程序图标
nomatter
2019-04-21 09:07:34
学习一下,正好需要用到这个功能

发送私信

遇顺境,处之淡然;遇逆境,处之泰然

9
文章数
8
评论数
eject