2011-12-26 117 views

回答

4

所有的Shell擴展處理程序都是進程中的組件對象模型(COM)對象。必須按照註冊外殼擴展處理程序中的描述爲它們分配一個GUID並進行註冊。它們以DLL形式實現,並且必須導出以下標準函數: DllMain。標準入口點的DLL。 DllGetClassObject。公開對象的類工廠。 DllCanUnloadNow。 COM調用此函數來確定對象是否正在服務任何客戶端。如果不是,系統可以卸載DLL並釋放關聯的內存。 與所有COM對象一樣,Shell擴展處理程序必須實現一個IUnknown接口和一個類工廠。大多數還必須在Windows XP或更早版本中實現IPersistFile或IShellExtInit接口。這些由Windows Vista中的IInitializeWithStream,IInitializeWithItem和IInitializeWithFile取代。 Shell使用這些接口來初始化處理程序。 的IPersistFile接口必須通過以下實現:

  • 圖標處理程序
  • 數據處理
  • 刪除處理

的IShellExtInit接口必須通過以下實現:

  • 快捷菜單處理程序
  • 拖和下降處理程序
  • 屬性表的處理程序

實施IPersistFile 的IPersistFile接口被設計爲允許從加載或保存到磁盤文件中的對象。除了IUnknown之外,它還有六種方法,它有五種,它從IPersist繼承的GetClassID方法。使用Shell擴展,IPersist僅用於初始化Shell擴展處理程序對象。由於通常不需要讀取或寫入磁盤,因此只有GetClassID和Load方法需要非執行語言。 Shell首先調用GetClassID,該函數返回擴展處理程序對象的類標識符(CLSID)。 Shell然後調用Load並傳入兩個值。第一個是pszFile,它是一個Unicode字符串,其中包含Shell即將運行的文件或文件夾的名稱。第二個是dwMode,它表示文件訪問模式。由於通常不需要訪問文件,因此dwMode通常爲零。該方法根據需要存儲這些值供以後參考。 以下代碼片段說明了典型的Shell擴展處理程序如何實現GetClassID和Load方法。它旨在處理ANSI或Unicode。 CLSID_SampleExtHandler是擴展處理程序對象的GUID,CSampleShellExtension是用於實現接口的類的名稱。 m_szFileName和m_dwMode變量是用於存儲文件名稱和訪問標誌的私有變量。

class CSampleShellExtension : public IPersistFile 
{ 
    // Method declarations not included 

    private: 
    WCHAR m_szFileName[MAX_PATH]; // The file name 
    DWORD m_dwMode;     // The file access mode 
} 

IFACEMETHODIMP CSampleShellExtension::GetClassID(__out CLSID *pCLSID) 
{ 
    *pCLSID = CLSID_SampleExtHandler; 
} 

IFACEMETHODIMP CSampleShellExtension::Load(PCWSTR pszFile, DWORD dwMode) 
{ 
    m_dwMode = dwMode; 
    return StringCchCopy(m_szFileName, ARRAYSIZE(m_szFileName), pszFile); 
} 

//下一節繼續實施示例。

實施IShellExtInit 的IShellExtInit接口只有一個方法,IShellExtInit ::初始化,除的IUnknown。該方法有三個參數,Shell可以使用這些參數傳遞各種類型的信息。傳入的值取決於處理程序的類型,有些可以設置爲NULL。 pidlFolder保存文件夾指向項目標識符列表(PIDL)的指針。這是一個絕對的PIDL。對於屬性表擴展名,這個值是NULL。對於快捷菜單擴展,它是包含其快捷菜單正在顯示的項目的文件夾的PIDL。對於非默認的拖放處理程序,它是目標文件夾的PIDL。 pDataObject包含一個指向數據對象的IDataObject接口的指針。數據對象以CF_HDROP格式保存一個或多個文件名。 hRegKey包含文件對象或文件夾類型的註冊表項。 IShellExtInit :: Initialize方法根據需要存儲文件名,IDataObject指針和註冊表項以備將來使用。以下代碼片段說明了IShellExtInit :: Initialize的實現。爲了簡單起見,此示例假定數據對象只包含一個文件。通常,數據對象可能包含多個文件,每個文件都需要提取。

// This code continues the CSampleShellExtension sample shown in the 
// "Implementing IPersistFile" section above. 

class CSampleShellExtension : public IShellExtInit { 
    // Method declarations not included 

    private: 
    // IDList of the folder for extensions invoked on the folder, such as 
    // background context menu handlers or nondefault drag-and-drop handlers. 
    PIDLIST_ABSOLUTE m_pidlFolder; 

    // The data object contains an expression of the items that the handler is 
    // being initialized for. Use SHCreateShellItemArrayFromDataObject to 
    // convert this object to an array of items. Use SHGetItemFromObject if you 
    // are only interested in a single Shell item. If you need a file system 
    // path, use IShellItem::GetDisplayName(SIGDN_FILESYSPATH, ...). 
    IDataObject *m_pdtobj; 

    // For context menu handlers, the registry key provides access to verb 
    // instance data that might be stored there. This is a rare feature to use 
    // so most extensions do not need this variable. 
    HKEY m_hRegKey;    } 
     // This method must be very efficient. Do not do any unnecessary work here. 
    // Use Initialize to acquire resources that will be used later. 
    IFACEMETHODIMP CSampleShellExtension::Initialize(__in_opt PCIDLIST_ABSOLUTEpidlFolder,__in_opt IDataObject *pDataObject, __in_opt HKEY hRegKey) 
    { 
    // In some cases,handlers are initialized multiple times. Therefore, 
    // clear any previous state here. 
    CoTaskMemFree(m_pidlFolder); 
    m_pidlFolder = NULL; 

    if (m_pdtobj) 
    { 
     m_pdtobj->Release(); 
    } 

    if (m_hRegKey) 
    { 
     RegCloseKey(m_hRegKey); 
     m_hRegKey = NULL; 
    } 

    // Capture the inputs for use later. 
    HRESULT hr = S_OK; 

    if (pidlFolder) 
    { 
     m_pidlFolder = ILClone(pidlFolder); // Make a copy to use later. 
     hr = m_pidlFolder ? S_OK : E_OUTOFMEMORY; 
    } 

    if (SUCCEEDED(hr)) 
    { 
     // If a data object pointer was passed into the method, save it and 
     // extract the file name. 
     if (pDataObject) 
     { 
      m_pdtobj = pDataObject; 
      m_pdtobj->AddRef(); 
     } 

     // It is uncommon to use the registry handle, but if you need it, 
     // duplicate it now. 
     if (hRegKey) 
     { 
      LSTATUS const result = RegOpenKeyEx(hRegKey, NULL, 0, KEY_READ, &m_hRegKey); 
      hr = HRESULT_FROM_WIN32(result); 
     } 
    } 

    return hr; } 

無論何時您創建或更改Shell擴展處理程序,都必須通知系統您已做出更改。通過調用SHChangeNotify來指定SHCNE_ASSOCCHANGED事件。如果您不調用SHChangeNotify,則在系統重新啓動之前可能無法識別更改。 還有一些其他因素適用於Windows 2000系統。有關詳細信息,請參閱Windows 2000系統上的註冊Shell擴展處理程序部分。 與所有組件對象模型(COM)對象一樣,您必須使用與Windows軟件開發工具包(SDK)一起提供的工具(如Guidgen.exe)爲該處理程序創建GUID。在HKEY_CLASSES_ROOT \ CLSID下創建一個子項,其名稱是該GUID的字符串形式。由於Shell擴展處理程序是進程內服務器,因此還必須在該GUID子項下創建一個InprocServer32子項,並將(默認)值設置爲處理程序DLL的路徑。使用公寓線程模型。一個例子如下所示:

HKEY_CLASSES_ROOT CLSID 
     {00021500-0000-0000-C000-000000000046} 
      InprocServer32 
      (Default) = %windir%\System32\Example.dll 
      ThreadingModel = Apartment 

任何時候殼牌需要可以涉及一個外殼擴展處理程序的動作時,它檢查適當的註冊表子項。註冊擴展處理程序的子項控制何時調用它。例如,當Shell爲文件類型的成員顯示快捷菜單時,通常會調用快捷菜單處理程序。在這種情況下,處理程序必須在文件類型的ProgID子項下注冊。

+1

這是一個非常好的答案我想在[更基本的問題](http://stackoverflow.com/questions/8438066)中進行概述。我想你會得到賞金。但我可以在哪裏更改類別?在截圖上的紅色框。 – rekire 2012-01-25 08:12:11

+1

很棒的分析....真的很有幫助。 – 2012-03-21 07:31:13