最近,好多人問我如何通過寫個小程序,動態(tài)替換可執(zhí)行文件的圖標。這個問題看起來雖小,但卻涉及到很多問題。網(wǎng)上也只能找到一些零零散散的資料,卻沒有詳細的指導(dǎo)性文檔。所以我決定把這個問題寫下來,以方便大家查閱。
EXE文件圖標的替換有很多方法,例如用一個EXE文件的圖標替換另外一個EXE文件的圖標;用一個ICO文件內(nèi)的圖標替換EXE文件的圖標。這兩種情況替換的方法不太相同,下面會詳細討論。
EXE文件圖標的替換更一般的情形,是PE(Portable
Executable)文件圖標的替換。只不過Windows操作系統(tǒng)只會顯示EXE文件的圖標罷了。但DLL、OCX等PE文件也都可以包含圖標資源。
下面我們從ICO文件格式說起,一步步講解替換EXE文件圖標的方法和原理。
.ico文件中圖標的保存格式
對于一個擴展名是.ico的文件,大部分人會認為一個ICO文件里面只能包含一個圖標。但事實上,一個ICO文件里面可以包含很多圖標。而且,
目前大部分ICO文件里面都包含有不同尺寸、不同色深的好幾個圖標。我們以MSN安裝包里的msnmsn.ico為例,這個圖標文件就包含了9個不同尺
寸、不同色深的圖標,如圖所示:

這樣做的目的,是為了保證不同的操作系統(tǒng)、不同的桌面色深,圖標顯示均可達到最佳效果。操作系統(tǒng)會選擇并顯示一個最合適的圖標。Windows
XP支持32位色的圖標,Windows 2000最多只支持256色的圖標。所以,如果我們開發(fā)的軟件若要同時支持Windows
XP和2000,那么為了達到視覺上的最佳效果,每一個ICO文件應(yīng)至少包含兩個圖標,一個是32位色的,一個是256色的。
ICO文件頭部結(jié)構(gòu)定義如下:
6 |
ICONDIRENTRY idEntries[1]; |
idCount表示該ICO文件包含圖標的數(shù)量,所以理論上,一個ICO文件最多可以包含65535個圖標。接下來,是該文件所包含的每一個圖標的描述。
11 |
} ICONDIRENTRY, *LPICONDIRENTRY; |
ICONDIRENTRY中記錄了每一個圖標的尺寸、色深、圖標資源占用的字節(jié)數(shù)。dwImageOffset是一個文件偏移地址,指向圖標資源數(shù)據(jù)起始位置。至于每一個圖標資源內(nèi)部的具體格式,與本文關(guān)系不大,這里就不再詳細介紹了。
PE文件中的圖標保存格式
PE文件中的圖標保存格式與.ico文件中圖標的保存格式略有不同。PE文件中,把ICONDIR和圖標資源作為兩種資源類型分別保存,前者是
RT_GROUP_ICON類型,后者是RT_ICON類型。為了與.ico文件中圖標的保存格式做以區(qū)分,我們把PE文件中的圖標保存格式重新定義如
下:
10 |
GRPICONDIRENTRY idEntries[1]; |
11 |
} GRPICONDIR, *LPGRPICONDIR; |
23 |
} GRPICONDIRENTRY, *LPGRPICONDIRENTRY; |
這里有一個區(qū)別,就是在.ico文件中,ICONDIRENTRY結(jié)構(gòu)最后一個成員dwImageOffset表示的是圖標資源文件偏移地址。而PE文件中,GRPICONDIRENTRY結(jié)構(gòu)最后一個成員nID表示的是圖標的索引ID。
Windows API
Windows操作系統(tǒng)為我們提供了幾個API函數(shù),用來更新PE文件中資源的函數(shù)有:BeginUpdateResource,
UpdateResource,
EndUpdateResource。用來枚舉PE文件中資源的函數(shù)
有:EnumResourceTypes,EnumResourceNames,EnumResourceLanguages。具體的使用方法可以參見
MSDN。
下面我們通過具體的例子,來驗證上面的方案是否可行。
用一個EXE中的圖標替換另外一個EXE文件的圖標
在這個例子中,我們用Windows XP自帶的記事本的圖標替換計算器的圖標。
記事本圖標
計算器圖標
下面代碼演示了如何替換32×32 32bits的圖標:
01 |
HMODULE hModule = ::LoadLibrary( "notepad.exe" ); |
02 |
HRSRC hResInfo = ::FindResource(hModule, MAKEINTRESOURCE(8), RT_ICON); |
03 |
HGLOBAL hGlobal = ::LoadResource(hModule, hResInfo); |
04 |
DWORD dwSize = ::SizeofResource(hModule, hResInfo); |
05 |
void * pData = ::LockResource(hGlobal); |
07 |
HANDLE hUpdate = ::BeginUpdateResource( "calc.exe" , FALSE); |
08 |
VERIFY(::UpdateResource(hUpdate, RT_ICON, MAKEINTRESOURCE(7), |
09 |
MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), |
11 |
VERIFY(::EndUpdateResource(hUpdate, FALSE)); |
12 |
VERIFY(::FreeLibrary(hModule)); |
大家肯定有個疑問,上面代MAKEINTRESOURCE(8)和MAKEINTRESOURCE(7)是怎么來的呢?其實索引8和7分別是
notepad.exe和calc.exe中,32×32
32bits圖標的索引。我們可以通過加載RT_GROUP_ICON資源,然后遍歷GRPICONDIRENTRY中每一個圖標的大小、色深,找到這個
圖標的索引。為了簡便,這里直接寫死的索引號,省略了這一動態(tài)查找的過程。
還有一個疑問應(yīng)該就MAKELANGID(LANG_ENGLISH,
SUBLANG_DEFAULT)了。PE文件中,每一個資源都至少對應(yīng)一種語言。因為我的操作系統(tǒng)是英文的,所以記事本和計算器中的圖標資源語言也是英
文的。對于簡體中文Windows XP操作系統(tǒng)所自帶的記事本和計算器,這個值應(yīng)該是MAKELANGID(LANG_CHINESE,
SUBLANG_SYS_DEFAULT)。
那么我們怎么才能知道一個PE文件中,圖標資源的語言是什么呢?我們可以通過資源枚舉API,枚舉所有圖標、語言??梢詤⒖忌厦嫣岬竭^的那幾個API
函數(shù),并查閱MSDN獲取這些函數(shù)的幫助文檔。 我們用記事本32×32
32bits圖標替換計算器同樣尺寸、色深的圖標后,效果如下,在Titles顯示方式下,圖標大小是48×48的,圖標沒有被改變:

在Icons顯示方式下,圖標大小是32×32的,圖標被我們改變了:

用一個ICO文件中的圖標替換另外一個EXE文件的圖標
用ICO文件中的圖標替換EXE文件圖標稍微有點麻煩,我們必須借助數(shù)據(jù)結(jié)構(gòu)ICONDIR和ICONDIRENTRY來完成。我們使用msnms.ico中的32×32 32bits圖標替換計算器中同樣大小色深的圖標:
01 |
DWORD dwSize = sizeof (ICONDIRENTRY); |
03 |
HANDLE hFile = ::CreateFile( "msnms.ico" , GENERIC_READ, FILE_SHARE_READ, |
04 |
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
05 |
::SetFilePointer(hFile, sizeof (ICONDIR) + dwSize * 6, NULL, FILE_BEGIN); |
09 |
VERIFY(::ReadFile(hFile, &Entry, dwSize, &dwRead, NULL)); |
11 |
::SetFilePointer(hFile, Entry.dwImageOffset, NULL, FILE_BEGIN); |
13 |
void * pData = new char [Entry.dwImageOffset]; |
14 |
VERIFY(::ReadFile(hFile, pData, Entry.dwBytesInRes, &dwRead, NULL)); |
16 |
HANDLE hUpdate = ::BeginUpdateResource( "calc.exe" , FALSE); |
17 |
VERIFY(::UpdateResource(hUpdate, RT_ICON, MAKEINTRESOURCE(7), |
18 |
MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), |
19 |
pData, Entry.dwBytesInRes)); |
20 |
VERIFY(::EndUpdateResource(hUpdate, FALSE)); |
25 |
VERIFY(::CloseHandle(hFile)); |
上面代碼中sizeof(ICONDIR) + dwSize *
6的意思是定位到第8個標結(jié)構(gòu)體ICONDIRENTRY的位置,這個圖標是32×32
32bits的。我們可以通過遍歷每一個ICONDIRENTRY來判斷,到底哪個圖標是這個尺寸的。這里我們?yōu)榱撕啽?,把這部分代碼省略了。
定位到第8個圖標結(jié)構(gòu)體ICONDIRENTRY的位置后,Entry.dwImageOffset的值就是第8個圖標資源的文件偏移地
址,Entry.dwBytesInRes的值是第8個圖標圖標資源的大小。然后我們將文件指針定位到Entry.dwImageOffset,并讀取
Entry.dwBytesInRes大小的數(shù)據(jù)到指針pData指向的內(nèi)存當(dāng)中。 最后,是替換文件圖標資源的代碼,這部分代碼跟上一個例子是相同的。
本文拋磚引玉,介紹了EXE文件圖標的替換,但完全可以推廣到所有PE文件圖標的替換(包括EXE、DLL等),也可推廣到所有PE文件資源的替換(包括圖標、圖片、文字資源、對話框模板、菜單等)??晒┫嚓P(guān)人員參考。