上善若水
          In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
          posts - 146,comments - 147,trackbacks - 0
          引用自:http://blog.163.com/yesaidu@126/blog/static/51819307200861853827582/

          Part I: A step-by-step tutorial on writing shell extensions

          第一節:Windows shell擴展初步:上下文菜單擴展

           

          作者:Michael Dunn

          譯者:yesaidu

           

          源代碼下載:1       2

           

          目錄

          README

          系列緒言

          第一部分緒言

          AppWizard開始

          初始化接口

          上下文菜單交互接口

          更改上下文菜單

          在狀態欄顯示拉線式(fly-by)幫助

          執行用戶選擇

          其他代碼細節

          注冊Shell擴展

          調試Shell擴展

          所有的外觀

          版權與許可

          修訂歷史

           

           

          README

                   我想,你在行動之前,或者你在本手冊的討論板發帖之前應該閱讀這份材料。

                   本手冊最初是用VC 6編寫的。現在,VC8都出來了,我感覺是時候對本手冊進行升級到VC7.1了。(通過VC7.1自動升級VC6項目,并不一定會完全地完成代碼轉換;因此,VC7.1用戶可能碰到這樣的現象,即在轉換、編譯示例代碼后,運行時可能沒有效果或出錯。)只要我仔細檢查并更新本手冊,本手冊將體現VC7.1的新特點。我將會提供VC7.1項目的源碼下載。

                   VC2005用戶要注意了:VC2005體驗版(Express edition沒有一同發布ATLMFC。既然本手冊用到了ATL,有時還使用了MFC,因此,你不能用VC2005體驗版來編譯示例代碼。

                   如果你正使用VC6,那么,你應該設法取得最新的平臺SDK。你可以使用WEB安裝版web install version),或者下載CAB文件或者ISO鏡像包,安裝它們到本地。確認把SDKINCLUDELIB目錄添加到了VC的搜索路徑中。你能在PSDK程序組中找到Visual Studio Registration目錄。這是一個好主意,無論你使用VC7,還是用VC8,你都能取得最新的PSDK頭文件和庫文件。
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

          VC7用戶注意了:如果你沒有更新PSDK必須改變默認的INCLUDE路徑。確信“VC++目錄”-“包含文件”列表的第一項是$(VCInstallDir)PlatformSDK\include,它在($VCInstallDir)include前面,如下圖:
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

                   由于一直沒有使用過VC 8,因此我不確定示例代碼在VC 8上是否可以通過編譯。只是希望,把VC7項目升級到VC8的自動轉換功能比從VC6VC7的要好些。如果你使用VC8編譯示例時遇到了任何疑惑,請在討論板發帖。

           

           

          手冊緒言

                   所謂shell擴展就是能增加某些功能到Windows資源管理器的COM對象。Shell擴展有很多內容,但關于它們的文檔資料卻非常少見。(自從我最先發表這份手冊的六年來,我相信情況要好多了。)如果你想深入Windows shell的內部,極力推薦Dino Esposito的巨作Visual C++ Windows Shell Programming (ISBN 1861001843)。對于沒有這本書的人,或者僅僅對shell 擴展感興趣的朋友,我將給你一個驚喜:一本有關shell 擴展編程的傻瓜手冊。即使本手冊并未讓你感到驚喜,那么,對你理解如何編寫shell擴展也會提供很好的幫助。本手冊假定你理解并掌握了COMATL的基本原理和應用。如果你還需要學習COM基本原理,請參考Intro to COM

                   第一節介紹了shell擴展的概要,并提供了一個上下文菜單擴展的示例,使你對后面的章節充滿興趣。

                   從字面上看,shell擴展包括兩個方面:shell和擴展。所謂shell,就是資源管理器Explorer;而擴展就是指在預定的事件發生時由Explorer調用執行的代碼(比如,在.DOC文件上右擊)。因此,shell擴展就是為Explorer增添功能的COM對象。

                   shell擴展是一個進程內服務器,它實現了跟Explorer通信的接口。ATL是設計一個shell擴展,并使之運行的最簡單辦法;這樣你就不用為一遍又一遍的編寫QueryInterface()AddRef()而大傷腦筋。在Windows NT下調試shell擴展要更容易些,這點,我在后面還會談到。

                   Shell擴展有很多種類型,每一類型都有其被調用的時機:即每種類型在不同的事件發生時被調用執行。下表列出了一些較常見的類型,以及它們被調用的情況:

          類型

          被調用的時機

          它可以做什么

          Context menu擴展處理器

          用戶在文件對象或文件夾對象或目錄窗口背景(需要shell v 4.71+以上)單擊右鍵

          在上下文菜單中添加菜單項

          Property sheet擴展處理器

          文件屬性對話框顯示時

          在屬性對話框中定制屬性頁

          Drag and drop擴展處理器

          用戶用右鍵拖放文件到文件夾窗口或桌面時

          在上下文菜單中添加菜單項

          Drop handler擴展處理器

          用戶拖對象并將其放到文件上時

          任何你想做的

          QueryInfo 擴展處理器
          (
          需要shell version 4.71+)

          用戶在文件、“我的電腦”等其他shell對象的圖標上懸停時

          返回一個Explorer顯示在工具提示中的字符串


           

           

           

          第一節緒言

                   現在,你可能有很多的疑問:為什么擴展看起來像Explorer?它到底是什么樣的?一個例子就是WinZip(或者WinRAR,我沒安裝WinZip ^_^――譯者)——它包含了多種shell擴展,其中之一就是上下文擴展。下圖是WinZip(其實是WinRA ^_^――譯者)為壓縮文件在上下文菜單中添加的菜單項:

          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

                   WinZip編寫了增加菜單項的代碼,提供了Explorer狀態欄上的菜單項幫助提示(fly-by help),并在用戶選擇一個WinZip菜單命令時執行相應的操作。

                   WinZip還提供了拖曳擴展處理,此類型跟上下文菜單擴展非常相似,但它是在用戶通過右鍵拖曳文件時才被觸發。下圖是WinZip(也是WinRA ^_^――譯者)拖曳文件彈出的菜單項:
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

                   還有很多的shell擴展類型,Microsoft不斷向每一個新的Windows版本中增加更多的類型。現在,讓我們把注意力放到上下文菜單擴展上,因為它易于編寫,效果也很明顯(能夠立即讓你滿意)。

                   在動手編碼之前,有一些便于編碼和調試的小技巧:當Explorer調用shell擴展(由用戶觸發)后,shell擴展暫時駐于內存中;此時,你無法重新編譯此擴展的DLL文件。為讓Explorer更迅速卸載擴展,可以在注冊表中創建下面的鍵:

          HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\AlwaysUnloadDLL

          并設置其默認值為“1”。在Win9x平臺上,這是最好的辦法。在WinNT上,可以在下面的鍵

          HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer

          創建一個DWORD DesktopProcess,也設置它的值為1。(譯者:如下圖,Win9x系統太少見了)這使得“桌面”和“任務欄”運行于一個進程,其他的Explorer窗口運行在其獨立進程。這意味著,你可以調試單個Explorer窗口,當你關閉該窗口時,相關的擴展DLL就會被自動卸載,這樣就避免了DLL文件正被Windows使用而無法替換的問題。要使注冊表修改生效,需要注銷后重新登錄。

          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

                   稍后,我將說明Win9x下如何進行調試。

           

           

          使用AppWizard開始

                   我們先做一個簡單的擴展,它僅僅彈出一個消息框以表明工作正常。我們把它關聯到文本文件,這樣,當我們在一個文本文件上右擊時,該擴展就會被調用。

                   好了,讓我們開始吧!什么?我還沒有告訴你如何使用那些神秘的shell擴展接口?別著急,我會邊進行邊解釋。我覺得,給出一個概念,緊跟著一個示例代碼,這樣做有助于理解。當然,我也可以先解釋所有的概念,然后列出示例代碼,不過這樣很難吸引注意力。不管怎樣,開啟你的VC,我們要開始了。

                   運行AppWizard,生成一個名為“SimpleExt”的ATL COM工程:

          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客去掉屬性化,保留其它默認選項,點擊“完成”。
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客
          現在,我們有了一個空的ATL項目,它可以編譯生成一個DLL,但我們還需要添加shell擴展COM對象。在類視圖中,右擊“SimpleExt”項,選擇“新建ATL 對象” VC7,選擇“添加”→“添加類”,下圖。本文的環境是Windows XPVC7.1,因此附圖都是VC7的)。

          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

                   ATL對象向導中,第一頁已經選擇了“簡單對象”,點擊“下一步”。
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

          在第二頁,在“簡稱”編輯框中輸入“SimpleShlExt”(其他編輯框會自動完成):
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

                   在默認情況下,向導將創建以C和腳本客戶端為基礎的OLE自動化兼容的COM對象。我們的擴展僅僅由Explorer調用,因此我們去掉自動化支持。在“屬性”頁,選擇“接口”類型為“自定義”,并且選擇“聚合”為“否”:
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

                   點擊“完成”,就創建了CSimpleShlExt類,它包含了實現COM對象的最基本代碼。我們將向這個類加入代碼。

           

           

          初始化接口

                   當我們的shell擴展被加載時,Explorer將調用QueryInterface()函數,以取得IShellExtInit接口指針。該接口僅有一個方法Initialize(),其函數原型如下:

          HRESULT IShellExtInit::Initialize (

                                         LPCITEMIDLIST pidlFolder,

                                         LPDATAOBJECT pDataObj,

                                         HKEY hProgID )

          Explorer通過該方法向我們傳遞各種各樣的信息。pidlFolder是用戶所操作文件所在的文件夾的PIDLPIDL [pointer to an ID list],指向ID列表的指針,是一個數據結構,它唯一標識了在shell空間的任何對象,這個對象是或者不是文件系統的對象。) pDataObj是一個IDataObj接口變量,通過它可以取得用戶正操作的文件名。 hProgID是一個HKEY接口變量,通過它可以取得擴展DLL的注冊信息。在本例中,僅僅需要pDataObj參數。

                   要添加這一方法到我們的COM對象,打開文件SimpleShlExt,加入下列粗體的行。AppWizard生成了一些不必需的COM關系代碼,既然我們不實現我們自己的接口,所以我指出這些失敗的能被移除的代碼(帶刪除線的那些):

                 #include <shlobj.h>

                 #include <comdef.h>

           

                 class ATL_NO_VTABLE CSimpleShlExt :

                    public CComObjectRootEx<CComSingleThreadModel>,

                    public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>,

                    public ISimpleShlExt,

                    public IShellExtInit

                 {

                    BEGIN_COM_MAP(CSimpleShlExt)

                        COM_INTERFACE_ENTRY(ISimpleShlExt)

                        COM_INTERFACE_ENTRY(IShellExtInit)

                    END_COM_MAP()

          COM_MAPATL實現QueryInterface的宏,它告訴ATL其它程序能從COM對象取得哪些接口。

                   在類聲明中,加入Initialize函數。此外,還需要一個變量來保存文件名:

                 protected:

                     TCHAR m_szFile[MAX_PATH];

           

                 public:

                     // IShellExtInit

                     STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY);

          接著,在文件SimpleShlExt.cpp中,添加Initialize的實現代碼:

                 STDMETHODIMP CSimpleShlExt::Initialize (

                                                    LPCITEMIDLIST pidlFolder,

                                                    LPDATAOBJECT pDataObj,

                                                    HKEY hProgID)

                   我們要做的是取得右鍵單擊選中的文件名,并把它顯示在消息框中。如果選中了多個文件,可以通過pDataObj接口指針來訪問它們,不過為了保持例子的簡單,我們只獲取第一個文件名。

                   文件名的格式和拖曳文件到WS_EX_ACCEPTFILES風格的窗口是的文件名格式一致,這樣說來,我們可以通過同樣的APIDragQueryFile來取得文件名。我們先取得包含在IDataObject中的數據句柄:

                 void CSimpleShlExt::Initialize (LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hProgID)

                 {

                     FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };

                     STGMEDIUM stg = { TYMED_HGLOBAL };

                     HDROP      hDrop;

           

                     // 在數據對象內查找CF_HDROP類型數據。

                     // 如果沒有數據,返回一個錯誤(“無效參數”)給Explorer

                     if ( FAILED( pDataObj->GetData ( &fmt, &stg ) ))

                        return E_INVALIDARG;

           

                     // 取得指向實際數據的指針。

                     hDrop = (HDROP) GlobalLock ( stg.hGlobal );

           

                     // 確保非NULL

                     if ( NULL == hDrop )

                        return E_INVALIDARG;

                   注意,錯誤檢查是極其重要的,尤其是對指針的檢查。因為我們的擴展運行于Explorer進程空間,如果我們的程序掛了,Explorer會跟著掛。在Win9x系統上,這樣的崩潰可能導致需要重啟系統。

                   現在,我們有了HDROP句柄,可以取得所需的文件名了:

                     // 有效性檢查,至少有一個文件名

                     UINT uNumFiles = DragQueryFile ( hDrop, 0xFFFFFFFF, NULL, 0 );

                     HRESULT hr = S_OK;

           

                     if ( 0 == uNumFiles )

                     {

                        GlobalUnlock ( stg.hGlobal );

                        ReleaseStgMedium ( &stg );

                        return E_INVALIDARG;

                     }

           

                     // 取得第一個文件名,保存到 m_szFile

                     if ( 0 == DragQueryFile ( hDrop, 0, m_szFile, MAX_PATH ) )

                        hr = E_INVALIDARG;

           

                     GlobalUnlock ( stg.hGlobal );

                     ReleaseStgMedium ( &stg );

           

                     return hr;

          }

          如果返回E_INVALIDARGExplorer不會在右鍵事件時再調用我們的擴展。如果返回S_OKExplorer將再次調用QueryInterface(),以取得另一接口:IContextMenu

           

           

          與上下文菜單交互的接口

                   一旦Explorer初始化了我們的擴展,它將調用IContextMenu方法來增加菜單項、提供狀態欄幫助(fly-by help),以及響應用戶的選擇。

                   添加IContextMenu接口與IShellExtInit相類似。打開文件SimpleShlExt.h,加入下列加粗的行:

          class ATL_NO_VTABLE CSimpleShlExt :

                       public CComObjectRootEx<CComSingleThreadModel>,

                       public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>,

                       public IShellExtInit,

                         public IContextMenu

                 {

                    BEGIN_COM_MAP(CSimpleShlExt)

                        COM_INTERFACE_ENTRY(IShellExtInit)

          COM_INTERFACE_ENTRY(IContextMenu)

                    END_COM_MAP()

          接著,添加IContextMenu方法的函數原型:

                 public:

                     // IContextMenu

                     STDMETHODIMP GetCommandString (UINT, UINT, UINT*, LPSTR, UINT);

                     STDMETHODIMP InvokeCommand (LPCMINVOKECOMMANDINFO);

                     STDMETHODIMP QueryContextMenu (HMENU, UINT, UINT, UINT, UINT);

           

           

          修改上下文菜單

                   IContextMenu有三個方法。第一個QueryContextMenu()修改上下文菜單。它的原型如下:

                 HRESULT IContextMenu::QueryContextMenu (

                                                    HMENU hmenu,

                                                    UINT uMenuIndex,

                                                    UINT uidFirstCmd,

                                                    UINT uidLastCmd,

                                                    UINT uFlags );

          hmenu是上下文菜單句柄。uMenuIndex是我們要添加菜單項的開始位置。uidFirstCmd uidLastCmd是菜單命令ID值范圍。uFlags表明Explorer調用QueryContextMenu()的緣由,這個后面還會談到。

                   關于此方法的返回值,你翻閱不同的文檔,可能得到不同的答案。Dino Esposito在他的書中認為這個返回值是所添加的菜單項的數目。但VC6MSDN卻說它是最后一個菜單項的命令ID1;然而,聯機MSDN卻說:

          如果函數成功,返回的HRESULT值就是分配的菜單項命令ID的最大差值加1。例如,uidFirstCmd5,你添加了3個菜單項,它們的命令ID分別是578。那么,返回值應該是MAKE_HRESULT(SEVERITY_SUCCESS,  0,  8 - 5 + 1)。否則,返回一個OLE錯誤。

                   一直以來,我都按照Dino的解釋來編寫代碼,這些代碼工作地很好。實際上,他的解釋與聯機MSDN是一致的,只要將uidFirstCmd作為第一項菜單項ID,后續的菜單項依次累加1

                   我們這里的擴展簡單的加入一個菜單項,因此QueryContextMenu()函數非常簡單:

           

                 STDMETHODIMP CSimpleShlExt::QueryContextMenu (

                     HMENU hmenu, UINT uMenuIndex, UINT uidFirstCmd,

                     UINT uidLastCmd, UINT uFlags )

                 {

                     // 如果標識包含了 CMF_DEFAULTONLY,那么,我們啥都不做

                     if ( uFlags & CMF_DEFAULTONLY )

                        return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 );

           

                     InsertMenu ( hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd, _T("簡單SHELL擴展測試") );

           

                     return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 1 );

                 }

                   首先,我們檢查uFlags的值。在MSDN內,你能找到所有的標識和它們的解釋,但對于上下文菜單擴展而言,僅僅一個值是有意思的:即CMF_DEFAULTONLY。該標識告訴shell命名空間擴展保留默認的菜單項;(如果設置了它的話,)shell擴展將不增加任何的菜單項。這也是我們為什么返回0的原因。如果未設置它,我們就可以通過句柄hmenu來修改菜單,并返回1告訴shell增加了一個菜單項。

           

           

          在狀態欄顯示提示幫助(fly-by help

                   下一個要被調用的IContextMenu方法是GetCommandString()。當用戶在Explorer窗口中右擊文本文件,或者選中文本文件后點擊“文件”菜單,鼠標指到我們添加的菜單項時,狀態欄將顯示提示信息。GetCommandString()函數返回一個字符串供Explorer顯示。

                   GetCommandString()原型如下:

                 HRESULT IContextMenu::GetCommandString (

                                      UINT idCmd, UINT uFlags, UINT* pwReserved,

                                      LPSTR pszName, UINT cchMax );

          idCmd是基于0的計數器,它表明了被選中的菜單項。由于我們只有一個菜單項,所以idCmd總為0。不過,如果我們添加了,比如說,3個菜單項,idCmd就是012uFlags是另外的一組標識,這個留待后面再討論。pwReserved可以被忽略。pszNameshell所有的緩沖區,用于顯示的幫助信息將拷貝到它中。cchMax是上述緩沖區的尺寸。返回值是HRESULT常量,比如說S_OKE_FAIL

                   GetCommandString()也能用來取得菜單項的“動作”(verb)。動作是與語言無關的串,它標識了能作用于文件對象的動作。關于這點,ShellExecute()的文檔中有更詳細的說明;有關動作的內容最好留待另外的文章(可以就這方面的內容另外一篇文章),這里簡要的說,是列在注冊表中的動作(比如“打開”和“打印”),或者有上下文菜單擴展動態創建的動作。這可以通過ShellExecute()來調用shell擴展的動作。

                   總之,我7li8li的說了這么多,就是為了解釋清楚GetCommandString()的作用。如果Explorer要提示信息,我們就給它;如果Explorer請求一個動作,就忽視它。這就是uFlags的作用。如果uFlags設置了GCS_HELPTEXT位,Explorer請求提示信息。另外,如果uFlags設置了GCS_UNICODE,我們必須給它一個UNICODE串。

                   本例中GetCommandString()如下:

                 #include <atlconv.h>  // ATL串轉換宏

           

                 STDMETHODIMP CSimpleShlExt::GetCommandString (

                     UINT idCmd, UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT cchMax )

                 {

                     USES_CONVERSION;

           

                     // 由于這里只有一個菜單項,所以idCmd 必須為0

                     if ( 0 != idCmd )

                        return E_INVALIDARG;

           

                     // 如果Explorer請求提示信息,拷貝串到提供的緩沖區

                     if ( uFlags & GCS_HELPTEXT )

                     {

                        LPCTSTR szText = _T("簡單的SHEEL擴展幫助(fly-by help)");

           

                        if ( uFlags & GCS_UNICODE )

                        {

                            // 這里,需要把 pszName 轉換為 UNICODE

                            lstrcpynW ( (LPWSTR) pszName, T2CW(szText), cchMax );

                        }

                        else

                        {

                            // ANSI版本

                            lstrcpynA ( pszName, T2CA(szText), cchMax );

                        }

           

                        return S_OK;

                     }

           

                     return E_INVALIDARG;

                 }

                   這沒什么奇怪的;我使用了硬編碼并將它轉換為合適的字符集。如果你從沒用過ATL轉換宏,你最好去學一下。它們在你向COM方法和OLE函數傳遞參數時,十分有用。

                   一個需要注意的重要事項是,lstrcpyn()函數保證字符串是以NULL結束的。這和CRTC運行時)函數strncpy()不同,后者在源串的長度大于等于cchMax時并不在串最后插入NULL。我建議總是使用lstrcpyn(),這樣就無需在調用strncpy()后總是檢查以確保串是否以NULL結束。

           

           

          執行用戶的選擇

                   IContextMenu接口最后的方法是InvokeCommand()。此方法在用戶點擊我們增加的菜單項后被調用,其函數原型如下:

                 HRESULT IContextMenu::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo );

          結構CMINVOKECOMMANDINFO9個成員,就我們的目的而言,僅僅需要關注lpVerbhwndslpVerb有兩個用途:它既可以是被引發的動作名,也可以是被點擊的菜單向索引。hwnds是用戶引發我們的擴展時所在的Explorer窗口句柄;我們可以將其作為我們用來顯示信息的窗口的父窗口。

                   由于我們只有一個菜單項,所以只需要檢查lpVerb:如果它為0,那么我們的菜單項就被選中了。最簡單的事情是彈出消息框,這里的代碼也就能干這事兒。消息框顯示了被選中的文件名,這表明代碼工作正常。

                 STDMETHODIMP CSimpleShlExt::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo )

                 {

                     // 如果 lpVerb 指向一個實際串,忽略此次調用并退出

                     if ( 0 != HIWORD( pCmdInfo->lpVerb ) )

                        return E_INVALIDARG;

           

                     // 取得命令索引,這里,唯一有效的值為0

                     switch ( LOWORD( pCmdInfo->lpVerb) )

                     {

                     case 0:

                        {

                            TCHAR szMsg [MAX_PATH + 32];

           

                            wsprintf ( szMsg, _T("被選中的文件:\n\n%s"), m_szFile );

           

                            MessageBox ( pCmdInfo->hwnd, szMsg, _T("SimpleShlExt"),

                               MB_ICONINFORMATION );

           

                            return S_OK;

                        }

                        break;

           

                     default:

                        return E_INVALIDARG;

                        break;

                     }

                 }

           

           

          其它代碼細節

                   這里,集中說明如何移除AppWizard生成的多余的OLE自動化特性方面的代碼。首先,可以移除SimpleShlExt.rgs(這個文件的用途在下一節詳述)中些注冊表入口:

              HKCR

              {

                 SimpleExt.SimpleShlExt.1 = s 'SimpleShlExt Class'

                 {

                     CLSID = s '{1CE1EBEB-1254-4880-B807-809CC31E8D2C}'

                 }

                 SimpleExt.SimpleShlExt = s 'SimpleShlExt Class'

                 {

                     CLSID = s '{1CE1EBEB-1254-4880-B807-809CC31E8D2C}'

                     CurVer = s 'SimpleExt.SimpleShlExt.1'

                 }

                 NoRemove CLSID

                 {

                     ForceRemove {1CE1EBEB-1254-4880-B807-809CC31E8D2C} = s 'SimpleShlExt Class'

                     {

                        ProgID = s 'SimpleExt.SimpleShlExt.1'

                        VersionIndependentProgID = s 'SimpleExt.SimpleShlExt'

                        InprocServer32 = s '%MODULE%'

                        {

                            val ThreadingModel = s 'Apartment'

                        }

                        val AppID = s '%APPID%'

                        'TypeLib' = s '{172391D4-B01E-4EF5-AC3E-34C99889D8B0}'

                     }

                 }

              }

                   我們也能移除DLL資源中的類型庫。(VC7)在“資源視圖”中,選中“SimpleExt.rc”,右擊,選中“資源包括”:
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

          在“資源包括”對話框的“編譯時指令”中有一行類型庫包括:
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

          移除這行,VC彈出警告,點擊“確定”:
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

                   移除類型庫后,我們還需要修改兩處代碼,以告訴ATL,它不應通過類型庫來處理。在文件SimpleExt.cpp中的DllRegisterServer()/DllUnregisterServer()函數,設置RegisterServer()/UnregisterServer()的參數為。

                 STDAPI DllRegisterServer (void)

                 {

                     // ...

                     return _Module.RegisterServer(TRUE FALSE);

                 }

          STDAPI DllUnregisterServer (void)

                 {

                     // ...

                     return _Module.UnregisterServer(TRUE FALSE);

                 }

           

           

          注冊Shell擴展

                   現在,我們實現所有的COM接口。不過,怎么才能讓Explorer使用我們的擴展呢?ATL自動生成注冊COMD服務器DLL 的代碼,但那是給其它程序使用。為了讓Explorer知道擴展存在,需要在文本文件的下述注冊表鍵下注冊我們的擴展:

                   HKEY_CLASSES_ROOT\txtfile

          在這個注冊表鍵下,名為ShellEx的鍵保存了有關文本文件的shell擴展列表;在它的下一級,名為ContextMenuHandlers的鍵保存了上下文菜單擴展列表。每個擴展都擁有一個ContextMenuHandlers的子鍵,其默認值為shell擴展的GUID。本文簡單擴展的示例,將創建如下子鍵:

                   HKEY_CLASSES_ROOT\txtfile\ShellEx\ContextMenuHandlers\SimpleShlExt

          并設置其默認值為擴展COMGUID,如“{1CE1EBEB-1254-4880-B807-809CC31E8D2C }”。

                   不必編寫代碼來完成COM的注冊。在“解決方案管理器”中有文件SimpleShlExt.rgs;這是一個文本文件,它被ATL解析,指導ATL在該服務器注冊時添加哪些鍵,在卸載時刪除哪些鍵。下面是注冊該擴展所要添加的注冊表鍵:

              HKCR

              {

                 NoRemove txtfile

                 {

                     NoRemove ShellEx

                     {

                        NoRemove ContextMenuHandlers

                        {

                            ForceRemove SimpleShlExt = s '{1CE1EBEB-1254-4880-B807-809CC31E8D2C}'

                        }

                     }

                 }

              }

          每一行都代表一個注冊鍵名。“HKCR”是HKEY_CLASSES_ROOT的縮寫。關鍵字NoRemove表明這個鍵在服務器卸載時不用刪除。關鍵字ForceRemove表明在寫入新鍵之前,如果該鍵存在,那么就要先刪除它;這行剩下的部分指定了一個字符串(這就是“s”的意思),它是SimpleShlExt的默認值。

                   這里,我插入幾句。我們的擴展注冊在HKEY_CLASSES_ROOT\txtfile下;然而,“txtfile”并不是持久的或者預先定好的名字。如果你查看一下HKEY_CLASSES_ROOT\.txt,它的默認值就是“txtfile”。這樣,有兩個副作用:

          1、  既然“txtfile”可能不是正確的鍵名,因此RGS腳本并不可靠。

          2、  某些文件編輯軟件可能在安裝到系統時,就把它自身關聯到文本文件。如果它改變了HKEY_CLASSES_ROOT\.txt的默認值,那么所有的shell擴展就不能用了。

          在我看來,這確實是一個設計上的漏洞。Microsoft可能也這么看,因為最新的擴展,如QueryInfo擴展就注冊在HKEY_CLASSES_ROOT\.txt下。

                 好了,到此為止。還有一個注冊的細節,即在WinNT上,為了讓非管理員帳號也能使用我們的擴展,得把它放到“approved ”擴展列表中:

                   HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved

          在這個鍵下,創建以擴展的GUID為名的串值,其內容任意。代碼在DllRegisterServer()DllUnregisterServer()中,都是一些簡單的注冊表訪問,我就不羅列了。你可以在示例代碼中找到。

           

           

          調試Shell擴展

                   總有一天,你將寫一個不是這么簡單的shell擴展;那時,你不得不調試它。打開項目的屬性對話框,在“調試”的“命令”編輯框輸入“C:\windows\explorer.exe”。如果是WinNT系統,設置DesktopProcess鍵(前述),當你按F5時就啟動了一個新的Explorer窗口。只要是在這個窗口完成所有的工作,那么在關閉這個窗口時,擴展就會被卸載,這樣就不影響后面的重建DLL

                   Win9x上,在運行調試器前,必須關閉shell:點擊“開始”,點擊“注銷”。按下Ctlr+Alt+Shift,并點“取消”。這將關閉Explorer,任務欄(桌面)消失了。切換到MSVC,按F5開始調試。要中止調試,按下“Shift+F5”關閉Explorer。完成調試后,可以從“開始”“運行”“Explorer.exe”,讓其正常啟動。

           

          擴展的外觀

          增加擴展后的上下文菜單項
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

          Explorer狀態欄提示(fly-by help
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

          彈出的消息框,顯示了所選中的文件名
          Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展 - yesaidu - yesaidu的博客

           

           

          版權與許可

          作者:Michael Dunn

          譯者:Yesaidu



          posted on 2012-07-12 11:11 DLevin 閱讀(3618) 評論(1)  編輯  收藏 所屬分類: 收藏

          FeedBack:
          # re: 【轉】Windows Shell擴展編程傻瓜手冊大全:上下文菜單擴展
          2014-06-25 12:51 | shell
          如果要所有文件都右擊都能顯示,要怎么改  回復  更多評論
            
          主站蜘蛛池模板: 和硕县| 垦利县| 内黄县| 开原市| 财经| 连江县| 普定县| 探索| 松原市| 舞阳县| 安西县| 澄江县| 阳江市| 平阴县| 隆尧县| 黄陵县| 平原县| 当雄县| 元氏县| 龙里县| 股票| 宝清县| 东兴市| 张家口市| 石林| 高唐县| 莱州市| 电白县| 阳原县| 灯塔市| 常州市| 金华市| 凤台县| 长海县| 渭南市| 潍坊市| 马边| 马关县| 永丰县| 甘谷县| 信宜市|