編寫可複用性更好的C++代碼——Band對象和COMToys(四)

來源:互聯網
上載者:User

編譯/趙湘寧

原著:Paul Dilascia

MSJ November 1999 & December 1999

關鍵字:Bands 對象,Desk Bands,Info/Comm Bands,Explorer Bar,Tool Bands。

本文假設你熟悉C++,COM,IE。

下載本文原始碼: MyBands.zip (128KB)
                TestEditSrch.zip (75KB)

第一部分:Band 對象介紹
第二部分:BandObj的類層次和MyBands服務程式的註冊
第三部分:深入Band內部,揭開Band的面紗

第四部分 Band對象使用中遇到的一些問題

    前面所描述的是一些Band對象的基本操作和處理,這些基本操作理論上都是按照你設計好的思路實現了所要的功能。但在實際的編程過程中,並不是每一件事情都按照它應該的方式進行。下面將告訴你有關Band對象使用中可能遇到的問題。
    文檔中說案頭Band必須實現IPersistStream介面來儲存持久性資料。到底是什麼資料呢?根據跟蹤診斷顯示,只有案頭Band 會查詢IPersistStream介面,而Explorer是不會查詢IPersistStream介面的,但要查詢IPersistStreamInit介面。那麼IPersistStream有什麼用呢?又一則隱藏在微軟例子代碼中的注釋是這樣說的:"如果使用者要拖拽案頭Band離開工作列到案頭上,則IPersistStream是必須要實現的介面。"如第一部分中的圖二所示,如果你不想要這個特性,就不用實現IPersistStream介面。
    TRACE還顯示Windows查詢IOleCommandTarget 和IDiscardableBrowserProperty介面。後面的這個介面是我特別喜愛的介面之一,它沒有函數!有人會問怎麼可能一個介面不帶函數呢?IDiscardableBrowserProperty只是用來通知IE:"如果你訪問另一個頁面,便可以丟棄我的頁面資料。它們是可消耗的。"有關IDiscardableBrowserProperty的詳細資料參見MSDN的另一篇文章:"Discardable Properties for Your Web Pages in Internet Explorer 4.0"。
    最後,TRACE揭示Windows還要查詢另一個神秘的介面,它就是——{EA5F2D61-E008-11CF-99CB-00C04FD64497},這個ID未出現在任何文檔裡面,源檔案或註冊表當中。如果有那位仁兄知道有關這個介面的資訊的話,請把它公諸於世。
    當你第一次建立案頭Band時,可能會遇到Windows不識別它的麻煩。但對於瀏覽欄(info和comm類型的Band),只需重新啟動一次IE或資源管理員即可。對於Windows不識別案頭Band的問題,只有一個解決方案就是用Ctrl-Alt-Del殺掉資源管理員進程或重啟機器。但最近我在新安裝的第二版Windows 98中測試時,發現我的案頭Band神秘地停止工作。不管我註冊多少次DLL和殺掉資源管理員進程都沒用,我的案頭Band拒絕出現。同樣的問題還發生在Windows 2000中。
    經曆了無數困惑和坎坷之後,我幾乎絕望了。這時我想起求助於MS的老大,他們可能會解決這個問題。得到的回覆是叫我參考微軟知識庫的有關文章--Q214842,這篇文章解開了這個秘密--"Windows 2000(第二版Windows 98也是同樣的問題)只在它感應到有一個啟動並執行安裝程式或者註冊表中沒有提供緩衝(cache)位置的時候才重新整理種類緩衝。" Windows"能感應"到安裝程式?。它是超人嗎?本文下面要揭示有兩種方法使得資源管理員重建立立種類緩衝:從setup.exe或者install.exe程式安裝(這就是所謂的超人力量,不過如此!)或刪除下面的這個註冊表鍵:
HKEY_CLASSES_ROOT/Component Categories/ {00021492-0000-0000-C000-000000000046}/Enum,它是一個緩衝。({00021492...}是指CATID_DeskBand)。我修改了BandObj.rgs檔案內容,總是刪除這個索引值。問題解決了。但是仍然要重啟外殼讓Windows產生新的種類緩衝。
    接下來是一串關聯的問題,它們都是由一個相同的bug導致的。這個問題就是:無論我什麼時候編譯原始碼,總是出現"不能用寫方式開啟MyBands.dll"。很明顯,只要資源管理員在運行,它就總是保持Band對象處於活動狀態。IE也一樣:當使用者隱藏或顯示Band時,IE調用IDockingWindow::HideDW 和ShowDW。IE不會釋放這個對象,直到它退出。對於案頭Band來說,這意味著你必須要再一次用Ctrl-Alt-Del來殺掉它,然後重建立立,很討厭,但那是沒有辦法的事。
    下面簡單說說調試問題,一般外殼擴充的調試可以參照精華區中一篇文章:"如何調試Windows外殼擴充"所講的辦法(竅門是用Ctrl+Alt+Shift關閉外殼,然後以explorer.exe作為調試進程運行調試器),但是,在調試案頭Band時。這個方法不靈,原因是最後總是鎖住DLL。然後又得重啟機器,真是糟透了,我真是厭煩 debugging 調試。幸好用TRACE也能跟蹤,這是我喜歡的一種調試工具。但偶爾要藉助於系統級的調試器。
    當我第一次實現MyBands時,在處理記憶使用者選中了哪一個搜尋引擎的地方不成功。那是因為我在對象的解構函式中儲存相應狀態——但不知什麼原因解構函式沒有被調用,就像我前面說過的,Windows沒有銷毀對象,它只是關閉了視窗。所以我將儲存代碼移到PostNcDestroy中才解決了問題。但是,如果即便使用者關閉了它,對象仍然存活的話,那就沒有必要儲存狀態設定,因為它們仍然還在記憶體中。不是嗎?
    所有這些小問題都是因為一個大問題導致的,通過TRACE的輸出顯示:每次你隱藏或顯示某個案頭Band時,資源管理員都建立一個新的Band對象。它決不會釋放舊的那一個!分析一下就知道了,如果CMyDeskBand是916個位元組,並且我有128MB RAM記憶體,在Windows記憶體耗盡之前,我能隱藏或顯示多少次對象呢?微軟已經承認了他們這個愚蠢的錯誤並答應不久就解決它。
    挑完了毛病,再回到Band的種類問題上,我在開始的時候談到了有三種類型的Band對象。其實還有第四種:那就是工具列Band。為了搞清楚這些術語的意思,我的頭已經被弄得蒙嚓鑔了,工具列Band位於IE 的Rebar中。IE5.0中的"電台"就是工具列Band的一個例子。(你不知道IE現在有一個無線電接收裝置嗎?小意思啦,IE6.0可能會有HDTV呢!)
    編寫工具列Band很容易。只要對info/comm類型的Band進行專門註冊就可以了;將Band類ID(GUID)作為串添加到HKEY_LOCAL_MACHINE/Software/ Microsoft/Internet Explorer/Toolbar下面。我已經修改了BandObj.rgs檔案來添加這個值,檔案名稱字叫ExplrBar.rgs。然後將這個檔案作為資源添加到工程的MyBands.rc檔案中。

IDR_COMMBAND REGISTRY DISCARDABLE "ExplrBar.rgs"

    在使用工具列Band時有一個問題。當你在資源管理員中單擊右鍵顯示這個Band的時候,它有一個"電台"功能表項目。實際上,它是第一個註冊的工具列Band的名字。這是一個已知的bug——參見微軟知識庫文章Q231621。Windows 2000好像已經解決了這個問題。

再談MyBands

  我想,現在你一定有點厭惡Band對象了,並希望它從此從你的生命中消失掉。真是個好主意!對於我來說,反正我已經把所有關於Band對象的東西全都告訴你了。在我走之前,請讓我再說說MyBands——實際實現Band的部分。它很簡單,但有些東西值得一提。
   首先,CMyCommBand和CMyInfoBand都是從一個公用基類CMyIEBand派生而來的。CMyIEBand實現了一個公用特性:即當你在Band上單擊滑鼠時,它給瀏覽器發送VC知識庫首頁和新浪首頁。為了實現這個功能,此Band對象需要IWebBrowser2介面,以便對象能調用IWebBrowser2::Navigate函數,讓Web瀏覽器知道訪問什麼地方。顯然,這需要一點訣竅,所以我首先想到編寫:

CComQIPtr iwb = m_pSite;     

    它當然要用QueryInterface來查詢IWebBrowser2介面,但QueryInterface的調用以E_NOINTERFACE錯誤和一個null 指標而告終。容器不實現IWebBrowser2介面。那麼到哪裡去獲得這個介面呢?請看下面這些你必須掌握的編程代碼:

CComQIPtr sp = m_pSite; m_spWebBrowser2 = NULL; if (sp) {   sp->QueryService(IID_IWebBrowserApp,                    IID_IWebBrowser2,                     (void**)&m_spWebBrowser2);}      

    IServiceProvider是一個用於COM對象的通用方法,通過它來提供它所知道的由其它對象實現的介面。初看起來它有點像QueryInterface,但又不完全是那樣:容器本身不實現IWebBrowser2介面,但是容器知道如何去獲得它的一個對象。
    接下來,讓我們研究一下CMyDeskBand(Web搜尋方塊)。這裡有趣的地方是編輯框控制,它在CEditSearch類中實現,為了在使用者按下"Enter"(斷行符號)鍵後接受到WM_CHAR訊息,我添加了一個WM_GETDLGCODE訊息處理器,其返回是DLGC_WANTALLKEYS。看看吧,所有Windows3.1的知識在這裡仍然有用武之地。當使用者按下"Enter"鍵時,CEditSearch截獲它,然後OnChar調用DoSearch函數利用使用者的輸入資訊來建立一個URL。這裡需要用"+"來替代空格(spaces),所以如果你用Yahoo搜尋"beanie baby sex"的話,則產生的URL將是:http://ink.yahoo.com/bin/query?p=beanie+baby+sex&z= 2&hc=0&hs=0,CEditSearch將傳遞這個串到IWebBrowser2::Navigate。CEditSearch中內建了幾個搜尋引擎,但你可以添加更多的搜尋引擎進去,方法是編輯MyBands.ini檔案。獲得這個檔案中URL最簡單的方法是到你最喜歡的門戶網站,用"MYSEARCH"作為搜尋索引鍵,並將結果URL從地址欄貝到MyBands.ini檔案中。
    說到 MyBands 所使用的這個INI檔案——其實在儲存簡單的應用程式配置資訊時,註冊表顯得非常笨重,所以我寫了一個小類:CIniFile來避免應用程式自動使用註冊表。

BOOL CMyBandsDll::InitInstance(){ // SetRegistryKey(_T("VCKBASE")); // 屏蔽掉註冊表!   CIniFile::Use(this, CIniFile::LocalDir); // 使用INI檔案!}      

    它告訴應用程式將INI檔案放到程式或DLL所在的目錄,不是Windows目錄。如果你不調用SetRegistryKey 的話,MFC將預設使用INI檔案,但它將這個檔案放在Windows目錄中。我喜歡將設定檔與使用它們的程式放在同一個目錄中,這樣你可以刪除整個目錄而不用關心會有額外的垃圾泛濫。如果你願意的話,CIniFile可以讓你選擇使用Windows目錄,或者指定一個不同的檔案名稱。方法是將應用程式的m_pszRegistryKey設定為NULL並且將m_pszProfileName設定為INI檔案的名字。當然,如果你使用INI檔案,你就不能自動獲得多個使用者使用的機器上由註冊表實現的使用者指定設定。所以說你看著辦吧,是控告我還是用SetRegistryKey。
    正如第三部分的圖十二所示,讓編輯框的操作功能表正常工作是要費點事的。當使用者在編輯框上單擊右鍵時,編輯框應該顯示自己的操作功能表,這個菜單中應包括"剪下/複製/粘貼"菜單。沒問題。只要重載WM_CONTEXTMENU訊息處理函數就可以了。但是MFC的命令中沒有這樣的東西——我是說針對編輯框的WM_CONTEXTMENU訊息處理函數,也就是說MFC的OnInitMenuPopup處理器是在CFrameWnd中實現的,而不是在CWnd中——儘管任何視窗都能顯示菜單。這個問題是個經常會在編程中碰到的問題。解決它的方法是利用我以前編寫的可以用於任何CWnd的一個小玩意兒:它就是CPopupMenuInitHandler,這個類派生於CSubclassWnd 。
    用CSubclassWnd可以動態子類化任何CWnd以實現訊息截獲。CPopupMenuInitHandler動態子類化編輯控制來截獲WM_CONTEXTMENU訊息。當截獲到這個訊息後便調用另一個類/函數——CPopupMenuInit::Init完成工作,這個函數與CFrameWnd中的差不多,幾乎是原封不動地拷貝過來的。CPopupMenuInit::Init為每個功能表項目建立了一個CCmdUI對象並將它發送到ON_UPDATE_COMMAND_UI處理器。任何時候你都可以象初始化MFC菜單那樣單獨使用CPopupMenuInitHandler。詳細代碼請參考MenuInit.cpp檔案。
    最終CEditSearch類比出了IE地址欄中的功能,即第一次點擊編輯框時,所有文本被選中。在單擊一次可定位游標。實現這些功能必須處理幾個訊息——WM_MOUSEACTIVATE,WM_ SETFOCUS,WM_LBUTTONDOWN以及滑鼠或鍵盤訊息——這個處理過程我都放在了CEditSearch::WindowProc之中。有時侯用老的C++做法比用訊息映射來得容易。有關細節請看原始碼。
    如果你調試過案頭Band的應用,你就會發現那是件痛苦的事情,我寫了一個單獨的程式就叫TestEditSrch,專門用來測試CEditSearch,確定它能正常工作後再將它放入案頭Band裡面(十五)。我強烈建議你也如法炮製。單獨寫出可執行代碼,然後再把它們移到Band中去。

圖十五

有關MyBands/BandObj的原始碼很長,全都是單調乏味的MFC嵌套類COM代碼。有興趣就去下載原始碼看吧!

小結

    可複用編程的實質是確定通用行為並將它封裝到可以在不同場合重複使用的類或子常式當中。對於Band對象而言,BandObj將Band對象精鍊成四個方面:類ID(GUID),MFC類,資源ID,以及種類ID。加上其它相關的資源。每一種Band對象,其COM介面及其資料之間的交換基本上大同小異。所以為什麼還要自己去寫全部的代碼呢?沒有理由不使用BandObj來建立與Band對象有關的應用。所有你要做的只是從架構類派生並調用AddBandClass。某些程式員會反對說BandObj依賴MFC及其龐大的MFC42.DLL。沒錯,但是我不認為那有什麼負擔,至少對於外殼擴充是這樣。MFC42.DLL被認為是Windows的一部分,它可以用於所有的Windows應用當中。
    如果你看一看CBandObj,就會明白它的通用性所在——不僅僅是針對Band對象,它適用於所有的COM類。CBandObj用CMenu和加速鍵表實現了IContextMenu和IInputObject。還要求Band對象做些什麼呢?它的IOleWindow 和IDockingWindow實現只需要一個CWnd。IObjectWithSite儲存指標,就這麼簡單。與Band對象有關的只有一個介面IDeskBand及其僅有的一個函數GetBandInfo。其它所有的東西都沒有什麼實質性內容。它儘可能地在更深的層次上抽象這些介面的實現,在小巧的類中使你能輕鬆組裝並一次又一次地在各種情況下重用這些介面。Shell檔案夾,檔案閱讀器,ActiveX控制項——所有一切。你只要組裝幾段代碼並編譯它就可以建立這些COM對象。這實在是太棒了。
    從下一部分開始,我將利用BandObj來建立自己COMToys,從而向你展示如何使用BandObj。(待續)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.