(本系列文章由檸檬的(lc_mtt)原創,轉載請注明出處,謝謝~)
關于注冊
動态庫必須注冊才能使用。除了使用 regasm 來注冊 DLL 以外,還應該在代碼中增加 RegisterServer 和 UnregisterServer 方法,以指導 DLL 注冊時,在 Windows 系統資料庫中增加什麼鍵。關于具體鍵以下做簡單說明:
1) 注冊 DLL 的 Shell Extensions。具體位置是 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved,增加以 GUID 為名稱的鍵,值則是動态庫說明。(此位置裡面全是 Shell 擴充的動态庫注冊,許多相關軟體就是從裡面擷取資訊,例如 ShexView。
2) 關聯檔案。Shell 擴充一般是針對檔案或者檔案夾的,是以必須關聯。許多人都熟知“HKEY_CLASSES_ROOT\*”的作用,就是用來關聯所有檔案。而檔案夾則是“HKEY_CLASSES_ROOT\Folder”。然而如果具體到某種檔案類型。可能會稍微複雜一點。我們可以從代碼中看到一些啟示:

RegTXT
對于關聯 .TXT,首先應該找尋“HKEY_CLASSES_ROOT\.txt”,但事情遠遠沒那麼簡單,因為相當多的文本編輯工具,都會把改鍵重定向,例如EMEditor會把改鍵的預設值改為“emeditor.txt”。被重定向後,我們為了不破壞原有關聯,應該到新的地方去注冊(如果沒有,我們就修改重定向至 TXT)。
無論是 *、檔案夾還是具體檔案類型,都會有 ShellEx 的鍵,為 Shell 擴充專用。具體不同的擴充,應該注冊不同的鍵。例如 ContextMenuHandlers、IconHandler、或者{00021500-0000-0000-C000-000000000046}(其實這就是QueryInfo)。注冊的方法很簡單,把預設值改為 GUID 即可。
相同檔案類型不同圖示?
如果是以前,我會對這句話十分吃驚。但現在這種現象比比皆是。除了我們的例子外,.NET 程式員最熟悉的莫過于 Sln 解決方案檔案了。不同版本的 Sln 圖示不同,上面有個小版本号提示。
不過後來我了解到,原來不同 Exe 顯示不同的圖示,也是這種原理,我暈。。。
擴充接口
圖示擴充處理器實作兩個接口 IPersistFile 和 IExtractIcon.
記得 IShellExtInit 接口用于一次有多個選擇檔案時的處理,而 IPersistFile 則用于初始化隻涉及一個選擇檔案時的處理。

IPersistFile 原型
對于這個接口,我們隻需要用到 Load 方法

Load
szFileName 是全局變量,用來記住目前操作的檔案路徑。
IExtractIcon 接口圖示擴充處理器實作 IExtractIcon 接口,當浏覽器需要為檔案顯示一個圖示時将調用該接口。
因為我們的擴充用于文本檔案,浏覽器将在每次顯示文本檔案對象時調用 IExtractIcon 的方法。
IExtractIcon 有兩個方法,它們的作用是告訴浏覽器所使用的圖示。記住:浏覽器為顯示的每一個檔案都将建立一個COM 對象。
這就是說每一個檔案都将有一個COM C++類對象對應. 是以在你的擴充中應該避免費時的操作以防止浏覽界面反應遲滞。

IExtractIcon 原型
和許多教程上面說的一樣,有兩種方法可将圖示傳回給浏覽器。但我在嘗試第一種方式的時候,未能成功,十分奇怪。不過我還是應該把這種方法簡單說明。
第一種是 GetIconLocation() 可以傳回檔案名/索引對以指出包含圖示的檔案,和圖示在該檔案中索引位置(以0為基)。Extract() 隻需傳回 S_FALSE 給浏覽器讓它自己來解析圖示。該方法的特别之處在于浏覽器在 GetIconLocation() 傳回之後不一定會調用 Extract().。浏覽器會保持一個圖示緩存以存儲最近使用的圖示。
如果 GetIconLocation() 傳回最近已使用的檔案名/索引對,而且圖示仍然在緩存中,浏覽器就可以直接使用緩存中的圖示而不會去調用Extract()。
第二種方法是從GetIconLocation() 中傳回不要檢視緩沖的标志,這樣會使浏覽器去調用Extract(),Extract() 則負責加載圖示資源并将其句柄傳回給浏覽器。
這裡具體介紹第二種方法。在這方法中,GetIconLocation() 作用僅僅是設定一些标志位,以及擷取檔案大小。

GetIconLocation
其參數為: uFlags 改變擴充行為的标志。
ExtractIconFlags.DONTCACHE 告訴浏覽器不要檢查圖示緩沖而去使用最近的 szIconFile/piIndex 對。其結果是IExtractIcon::Extract() 将被調用.。
ExtractIconFlags.NOTFILENAME 根據 MSDN,該标志告訴浏覽器當GetIconLocation()傳回時忽略 szIconFile/piIndex 的内容。
szIconFile 是由shell 提供的一個緩沖要求我們填入包含所使用的圖示的檔案名.
cchMax 是該緩沖區的大小。
piIndex int 的指針,要求我們添入圖示在檔案中的索引。
pwFlags UINT 的指針,要求我們傳回影響浏覽器行為的标志。
使用第二種方法,我們并不需要填寫 piIndex 和 szIconFile,而 IExtractIcon.Extract() 總被調用,并負責加載圖示并傳回兩個圖示句柄 HICON 給浏覽器 – 一個是大圖示, 一個是小圖示。該方法的好處是你不必考慮你的圖示資源在檔案中的順序位置。其缺陷在于它忽略了浏覽器的圖示緩沖,這會使顯示速度減慢,特别是在有浏覽有無數個檔案的目錄時。

IExtractIcon.Extract
其參數為:
pszFile/nIconIndex 檔案名和索引指定圖示位置。其值與從 GetIconLocation() 傳回的一樣。
phiconSmall HICON 的指針,由 Extract() 傳回指向大圖示和小圖示的句柄數組。
nIconSize 指定要求的圖示大小。高字為小圖示的長度 (長寬一緻),低字為大圖示的長度。在一般情況下, 其值為0x00100020 (高字16, 低字 32) 表示小圖示應該是 16x16,大圖示為 32x32。在我們的擴充中, 我們并沒有在 GetIconLocation() 裡填寫 pszFile 和 nIconIndex 是以在這忽略,我們隻加載圖示并傳回給浏覽器。
從代碼可以看到,根據檔案大小的不同,加載了相應的圖示資源傳回給浏覽器。效果如下:
關于代碼:代碼裡面還包括了提示擴充的代碼,如果有興趣,可自行閱讀。
題外話:還有相當多的關于 Shell 擴充的内容無法一一說明,如果有機會,以後會盡量補上。或大家查閱網上的“Windows Shell擴充程式設計完全指南”(雖然是VC版的,但内容相當豐富)