原作者:James Brown
Original Author: James Brown
原文連結:http://www.catch22.net/tuts/sysimg.asp
Original Link: http://www.catch22.net/tuts/sysimg.asp
點這裡下載樣本程式及其源碼
注意:本文的中文翻譯工作已得到了原文作者 James Brown 的郵件授權,如果您需要轉載,請聯絡 James 本人。
系統映像列表(有時亦被稱作 Shell 表徵圖緩衝)是一個由 Windows Shell 維護的表徵圖資源,資源管理員和其它應用程式使用這個列表來顯示系統對象、程式和檔案類型的表徵圖。其實就是一個簡單的 HIMAGELIST(可以用映像列表 API 存取的標準映像列表),一些應用程式可能會發現顯示系統提供的表徵圖更好一些,而不是自己內部儲存這些表徵圖的副本。所以,本教程的目的就是解說如何存取系統的映像列表,並在你的應用程式裡使用它。
簡單的方法
如果你先前從未花時間瀏覽過 MSDN 的話,你可能就會調用 SHGetFileInfo API(由 shell32.dll 匯出,Win9x/WinNT4/2000/XP 都可使用)。這個函數看起來是這樣的:
DWORD_PTR SHGetFileInfo(
LPCTSTR pszPath,
DWORD dwFileAttributes,
SHFILEINFO *psfi,
UINT cbFileInfo,
UINT uFlags
);
此函數可被用來獲得指定檔案的資訊,uFlags 參數則指定了要擷取的具體哪些資訊。對於這個參數來說,有兩個非常有趣的取值: SHGFI_ICON 和 SHGFI_SYSICONINDEX 。當指定了這兩個值之後,SHGetFileInfo 會返回系統映像列表的控制代碼,並將相應的表徵圖類型索引儲存到 SHFILEINFO 結構中。
下面的調用示範了可能的使用方法:
SHFILEINFO shfi;
HIMAGELIST hSysImgList;
...
hSysImgList = (HIMAGELIST)SHGetFileInfo(
"C:",
0,
&shfi,
sizeof(SHFILEINFO),
SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_ICON);
平台差異
在 Windows 9x 和 Windows NT 系列的作業系統之間,存在一個很微小但又十分重要的區別。在 Windows 9x 下,SHGetFileInfo 返回系統映像列表的控制代碼,這個控制代碼是被所有進程共用的。這個映像列表與資源管理員所使用的以及 Shell 用來顯示系統表徵圖的映像列表是同一個映像列表,這就意味著如果一個進程對這個映像列表進行了任何的誤操作,那麼所有的進程(包括資源管理員)都會受到影響。
在 Windows NT (以及 2000/XP )之下,就會略有不同。由於系統架構的不同,亦為保持一個穩定的作業環境,SHGetFileInfo 會為每個請求系統映像列表的進程返回一份單獨的拷貝。詳而述之,SHGetFileInfo 並不會像 Windows 9x 那樣返回一個完整的系統映像列表——只有在 SHGetFileInfo 調用中所請求的表徵圖會出現在返回的映像列表中。對於 99% 的情況來說並沒有什麼關係,但是由於我們要製作一個瀏覽整個映像列表的應用程式,這就向我們提出了一個問題。
在所有的 Windows 版本中定位系統映像列表
閑話休敘,在 shell32.dll 中有一個名為 Shell_GetImageLists 的未公開 API ,——你可能猜到了,它會返回系統映像列表的控制代碼(注意函數名的複數形式,有大表徵圖和小表徵圖的版本)。這個 shell32.dll 中未公開的 API 適用於所有的 Windows 版本。
BOOL WINAPI Shell_GetImageLists(HIMAGELIST *lphimlLarge, HIMAGELIST *lphimlSmall);
我們需要的另一個 API 是 FileIconInit, 它只適用於 Windows NT/2000/XP 。這不是問題,因為我們只需要在 Windows NT 下調用它。
BOOL WINAPI FileIconInit(BOOL bFullInit);
這些 API 並未用函數名匯出,而只有序號:
// shell32 未公開的函數
Shell_GetImageLists @71
FileIconInit @660
獲得了這一資訊後,我們現在可以編寫一個函數來返回系統映像列表的控制代碼了。第一步是定義兩個 typedef ,以使我們的工作更容易些。
typedef BOOL (WINAPI * SH_GIL_PROC)(HIMAGELIST *phLarge, HIMAGELIST *phSmall);
typedef BOOL (WINAPI * FII_PROC) (BOOL fFullInit);
SH_GIL_PROC 是 Shell_GetIconLists_Procecedure 簡寫,同理 FII_PROC 是 FileIconInit_Procedure 的簡寫。現在我們可以定位這些 shell32.dll 未公開的的函數了,以便調用。
HMODULE hShell32;
SH_GIL_PROC Shell_GetImageLists;
FII_PROC FileIconInit;
// 裝載 shell32.dll ,如果它尚未裝載的話
hShell32 = LoadLibrary("shell32.dll");
if(hShell32 == 0)
return FALSE;
//
// 從 shell32.dll 中擷取未公開的 API
//
Shell_GetImageLists = (SH_GIL_PROC) GetProcAddress(hShell32, (LPCSTR)71);
FileIconInit = (FII_PROC) GetProcAddress(hShell32, (LPCSTR)660);
請注意我們是如何把序號參數傳給 GetProcAddress 的。OK ,現在我們已經獲得了所需的函數指標,可以進行處理了。
// 初始化本進程的映像列表——在 Win95/98 下函數是停用
if(FileIconInit != 0)
FileIconInit(TRUE);
// 獲得大表徵圖和小表徵圖的系統映像列表控制代碼
Shell_GetImageLists(phLarge, phSmall);
// 不要卸載 shell32.dll !
上面程式碼片段最後的注釋指出“不要卸載 shell32.dll ”,這是非常重要的。因為只要 shell32.dll 被卸載,系統映像列表就會被銷毀,這樣一來我們是前的所有工作都會付之東流。
永遠不要修改或刪除系統映像列表!
在我們往下進行之前,你應該瞭解另外一條重要的資訊。你永遠不要去嘗試在系統映像列表中添加、刪除表徵圖,也不要刪除系統映像列表。這一點在 SHGetFileInfo 的文檔中已經解釋得很清楚了,而且這也是個很好的意見。但是,仍然有一個很簡單的方法會將系統映像列表誤刪。
現在來說這個問題。在預設情況下,當一個列表視圖(ListView)控制項被銷毀後,它會自動調用 ImageList_Destroy API 來刪除與之關聯的映像列表。也就是說,在預設的情況下你甚至要求你的程式刪除系統映像列表!——是的,當時就是這樣。所幸,列表視圖控制項有一個 LVS_SHAREIMAGELISTS 樣式可以防止它刪除它的映像列表。(在這種情況下,請確認你設定了這個樣式!)
十分詭異,樹型視圖(TreeView)控制項卻不會引起這一問題—— MSDN 指出樹型視圖不會銷毀與之關聯的映像列表,所以只有列表視圖控制項需要特別注意。
顯然,如果你的程式運行在 Windows NT/2000/XP 下,那麼誤刪系統映像列表並不會有很嚴重的後果,因為你只是刪除了自己進程中映像列表的拷貝。但是,在 Windows 95/98/ME 下,刪除這個映像列表會產生災難性的後果。不信就試試!
結論
擷取完整的系統映像列表本是一件非常棘手的事,尤其是如果我們不得不從緩衝表徵圖的檔案中提取表徵圖的話。所幸,這一對未公開的 API 函數可以幫我們做這些事情。當然,我不能把發現這些的功勞記在自己身上——我是在 James Holderness 優秀的網站上發現的,那裡關於系統映像列表的資訊比我這裡提到的更多,另外還有更多的未公開的好東西,現在就過去看看吧!
祝你們用得開心!
如果有任何的評論和建議,請給我發郵件:james@catch22.net