2011年6月12日 星期日

(C#)利用Microsoft COM組件變更檔按圖示(一)

Windows平台下檔案圖示的呈現、變更與清理,基本上應該是透過explorer.exe程序處理的。在系統安裝完成後系統對於一般檔案有其預設的檔案型態定義與圖式,雖然這些圖示可以透過管理介面變更,但其實都是在變更系統登錄資料庫中對檔案類型的icon值作定義,一旦變更後會改變系統中所有對此類型檔案的圖示加以變更,並無法針對單一檔案做處理。

至於新安裝的應用軟體和其所屬的檔案型態,在完成安裝時必須告訴explorer其對應的檔案副檔名與檔案圖示。

針對單一檔案圖示的變更,微軟也提供許多應用程式開發時的工具,透過不同的方式來變更單一檔案的圖示而不改變相同檔案類型的其他圖示,其中較為普遍的就是利用COM(Component)組件來完成這項工作。

由於系統的多功,複雜了系統的處理邏輯,微軟為了應付大量的系統功能變更與更新,將系統許多功能模組拆解成功能性的組件,這些組件都以shell interface的方式定義,裡面包含了相關的屬性與方法,利用物件繼承的特性實作這些方法與改變物件屬性便可以改變系統的判斷行為。Explorer.exe只需要針對用戶發出的event透過QueryInterface()查詢到相關的interface來處理用戶端的動作即可。

下面是我實作這些interface的展示:

http://msdn.microsoft.com/en-us/library/bb774328(v=VS.85).aspx

微軟定義了非常多的shell interface來給開發者使用,其中IExtractIcon定義了如何由物件中取出icon及其位置的方法,然後加以處理置換。

我利用這個interface來實作我所想要的動作,範例以c#來作處理,當然也可以用c++,不過目的都一樣。

(1) 編輯一個c#類別來定義要實作的shell interface

下面為IExtractIcon的原型,必須先定義。

[ComVisible(true), ComImport, Guid("000214eb-0000-0000-c000-000000000046"), 
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IExtractIcon
{
[PreserveSig]
int GetIconLocation([In] ExtractIconOptions uFlags,
[In] IntPtr szIconFile,
[In] uint cchMax,
[Out] out int piIndex,
[Out] out ExtractIconFlags pwFlags);
[PreserveSig]
int Extract([In, MarshalAs(UnmanagedType.LPWStr)] string pszFile,
uint nIconIndex,
[Out, MarshalAs(UnmanagedType.CustomMarshaler,
               MarshalTypeRef=typeof(IconMarshaler))] out Icon phiconLarge, 
[Out, MarshalAs(UnmanagedType.CustomMarshaler,
               MarshalTypeRef=typeof(IconMarshaler))] out Icon phiconSmall, 
[In] uint nIconSize);
}






此外變更圖示的動作要看是在甚麼樣的動作驅動下變更的,由msdn中可以查到一個叫IPersistFile的COM interface



http://msdn.microsoft.com/zh-tw/library/system.runtime.interopservices.comtypes.ipersistfile(v=vs.85).aspx



由這個interface可以讓應用程式知道用戶正在對檔案做甚麼樣的動作,下面為interface的原型,開發者必須在應用程式中實作下面方法。

    [ComImport(), ComVisible(true), InterfaceType(ComInterfaceType.


InterfaceIsIUnknown), GuidAttribute("0000010b-0000-0000-C000-000000000046")]
public interface IPersistFile
{
[PreserveSig]
uint GetClassID(out Guid pClassID);
[PreserveSig]
uint IsDirty();
[PreserveSig]
uint Load([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
                  [In] uint dwMode);
[PreserveSig]
uint Save([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
                  [In] bool fRemember);
[PreserveSig]
uint SaveCompleted([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName);

[PreserveSig]
uint GetCurFile([MarshalAs(UnmanagedType.LPWStr)] out string ppszFileName);
}
 






(2) 實作interface



實作的部分最主要是要處理當shell讀取或寫入檔案時如何置換檔案圖示。由範例中程式主要是判斷檔案內容來決定要用何種圖示取代。



if (text.ToUpper().StartsWith("GWGDE"))
{
Icon phicon = getOverlayIcon(defIcon, Resource1.encrypted);
phiconLarge = phicon;
phiconSmall = new Icon(phicon, new Size(16, 16));
}
else if (lngFileSize > 0)
{
phiconLarge = Resource1.decrypted;
phiconSmall = new Icon(Resource1.decrypted, new Size(16, 16));
}
else
{
Icon empty = Icon.FromHandle(ExtractSpecialIcon(defIcon).GetHicon());
phiconLarge = empty;
phiconSmall = new Icon(empty, new Size(16, 16));
}










由上面的程式碼處理後,對於檔案內容起始含有GWGDE字串及有無內容都會呈現出不一樣的檔案圖示。



(3) 註冊要處理的檔案型態



有了以上的處理動作,接下來就是要如何將程式安裝到系統中,當用戶在處理檔案讀寫時,explorer會針對檔案內容判斷後改變檔案圖示。



這個動作牽涉到註冊DLL檔案,必須利用RegAsm.exe註冊工具來註冊DLL,但DLL必須先實作RegisterServer和UnregisterServer來告訴RegAsm要註冊的動作,範例中我只使用.txt的檔案型態做動作



private static void RegTXT()
{
RegistryKey root;
RegistryKey rk;

root = Registry.ClassesRoot;
rk = root.OpenSubKey(".txt");
string txtclass = (string)rk.GetValue("");
if (string.IsNullOrEmpty(txtclass))
{
txtclass = "TXT";
rk.SetValue("", txtclass);
}
rk.Close();

rk = root.CreateSubKey(txtclass +
                 "\\shellex\\ContextMenuHandlers\\" + KEYNAME);
rk.SetValue("
", GUID);
rk.Close();

rk = root.CreateSubKey(txtclass + "
\\shellex\\IconHandler");
rk.SetValue("
", GUID);
rk.Close();

rk = root.CreateSubKey(txtclass +
                  "\\shellex\\{00021500-0000-0000-C000-000000000046}");
rk.SetValue("
", GUID);
rk.Close();
}
 






(4) 標示DLL為強式名稱(strong name)



要成功註冊DLL,必須先將DLL標示成強勢名稱,我們可以利用sn.exe工具來產生金鑰檔案



sn –k Key.snk



再透過專案屬性中的簽署來設定簽署檔案,如此編譯出的DLL就符合標準



clip_image002



(5) 利用RegAsm.exe註冊與註銷DLL



因為註冊工具有分成32和64位元的作業系統版本,所以在使用時要有所區分,我將動作寫在batch檔案中分成reg32.bat和reg64.bat,如果要註銷時須使用unreg32.bat和unreg64.bat。



安裝完後必須重新啟動explorer,使DLL載入;註銷完後也不會立即失效,因為此時只清除登錄資料庫中的資料,但DLL已載入到explorer中所以必須重新啟動explorer才能徹底移除該DLL。



此外因為動作牽涉到登錄資料庫,所以命令提示字元必須以管理員身分開啟,否則會註冊失敗。



(6)



由下面可以看出三個檔案都是文字檔(.txt),但會因為內容不同呈現出不同圖示



2011-06-12_225800











沒有留言:

張貼留言