編寫可複用性更好的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對象使用中遇到的一些問題

第五部分 建立自己的COM編程平台ComToys

概要:

    可重用類就像小巧的 COM 積木一樣,人們可以在不同場合以不同方式裝配這些 COM 來建立更加精緻的對象。但是如何建立這些 COM 呢?本文使用 MyBands 和 BandObj架構作為實驗平台開發了一個可重用庫:它就是 COMToys......。COMToys提供了一種用C++編寫COM的方法或途徑,這些方法和途徑適用於任何類型的COM對象,不論你是使用MFC。還是其它的什麼東西。COMToys是一種態度——它告訴人們用C++編寫COM組件並不難,情況也確實是這樣!
    前面我們討論了一個叫MyBands的程式,這個程式有功能之一就是可以將編輯框控制放到Windows的工作列中。此外,MyBands實現了三種Windows的Band對象,其中就有Web搜尋方塊,一所示:

圖一 工作列中的Web搜尋方塊

    為了實現MyBands程式,我編寫了一個迷你型Band對象架構:BandObj,其對應的實作類別是CBandObjDll,CBandObjFactory,和CBandObj。這個架構提供了編寫各種Band對象所需要的支援。利用這個Band對象架構,你不必做太多的工作就能開發出滿足自己需要的Band應用。
    CBandObj中有許多代碼實現不同的介面像IDeskBand,IOleWindow,IContextMenu等等,但只有一個介面與Band對象的處理有關,即IDeskBand介面。其它的代碼都與此無關。CBandObj中IContextMenu的實現沒有涉及到Band對象的任何東西;它只需要一個菜單。而IOleWindow只要一個視窗控制代碼。還有其它的一些實現情況也一樣。CBandObj架構儘可能地抽象這些介面並將它們封裝在可重用類中——像小巧的COM積木,你可以在不同場合以不同方式裝配這些COM來建立更加精緻的對象。那麼如何建立這些小巧的COM積木呢?
    本文將向你展示如何利用MyBands 和 BandObj架構作為測試平台來開發一個庫,這個庫的原始碼本文的例子程式:COMToys。COMToys並不像ATL或者MFC那樣與系統融為一體,它是一些更特別的宏、函數、類以及我認為能輕鬆編寫BandObj的東西的集合。但COMToys提供了一種用C++編寫COM的方法或途徑,它適用於任何類型的COM對象,不論你是使用MFC。還是其它的什麼東西。COMToys是一種態度——它告訴人們用C++編寫COM組件並不難,情況也確實是這樣!

COM:C++編程的困境

    自從有了COM,我不得不同情可憐的使用Java語言和Visual Basic語言的C++程式員。你會看到編寫"form.color = red"的程式員那得意的笑容,而C++駭客們正焦急的發出指令——QueryInterface…Get()…Set()——以及隨時提醒自己不要忘了檢查HRESULT!如果沒有對每一個AddRef調用Release,那就叫上帝幫你吧!哈哈哈……在Visual Basic中,你不用記住要敲入分號。與Visual Basic和Java語言比起來,C++更強大,更有傾向性,但一涉及到COM,其複雜性似乎讓人感到無望。這對COM是專為C++而設計是一個多麼大的諷刺啊!畢竟,COM對象只是一個C++虛表(vtbl)。
    問題並不是C++,也不是COM。而是大多數C++程式員對它們的瞭解還不夠。它們從SDK的例子中剪下、粘貼代碼——這些公開的長長的代碼只是用於示範最原始、最無遮掩的編程方法,從來沒有考慮如何面向系統設計。兄弟,那些東西不是產品代碼!不知你想過沒有,為什麼把它們叫做例子?C++的優點是讓你編寫重量級的程式。但沒有人能天天忍受那種編程虐待。如果你想容易,你就可以容易。只要你先花點時間建立一些工具,這些工具以後將會成倍地回報你的。Visual Basic和Java語言之所以容易是因為考慮了將基礎結構內建在語言系統中。但你也可以編寫自己的基礎結構來使得C++便於使用啊。
   前面所討論的MyBands程式有兩層結構,底層是架構BandObj,上面是應用MyBands。這兩層結構是出於示範目的人為建立的。在實際編程中,我用的是三層結構來建立BandObj架構,十六所示。此結構的基礎由COMToys提供。可以看出,只有把COMToys移走之後才能聚焦在Band對象上。現在就讓我們來揭示這個系統吧。

圖十六 MyBands的體繫結構

    我在設計MyBands/BandObj/COMToys的時候,必須做出一個今天許多程式員都要面對的決定:即使用什麼編程系統來編寫COM?是用ATL?,MFC?,COM+?,或者以上的都不用?儘管我常常喜歡賣弄一下自己,但是從來不拒絕使用別人做好的東西,只要它能工作並且容易使用。所以我考慮ATL和MFC兩者都用。
    ATL口碑很好並且便於使用,但誰能搞懂所有的關於它的術語和那些尖括弧呢?你真知道ATL產生了多少代碼嗎?如果這些代碼這麼適於在模板中通用,那為什麼不使用一個簡單的類或子常式取而代之呢?一點都沒錯,模板能將很多東西參數化,這一特性當然很棒。但我只想開一個雜貨店,沒有必要也不需要藉助太空梭。更重要的是,ATL缺乏圖形化使用者介面的支援,而像Band對象這樣的外殼擴充需要GUI支援。話又說回來,ATL的簡單易用,多繼承模型及智能指標的誘惑讓人無法拒絕,就象前面文章中曾用到的ATL的註冊器,它讓我愛不釋手。
    MFC又怎麼樣呢?它的GUI的誘惑力是不可抗拒的。即便你不需要文檔/視圖結構,它還有命令處理常式和ON_COMMAND_UPDATE_UI處理器——此乃任何UI對象之根本。但只要涉及到COM,MFC便遜色多多。大多數人都是擔心它那大象般體積的DLL,但依我之見,那沒什麼了不起,不錯,MFC42.DLL的確肥大,但是沒有了它,Windows能轉起來嗎?它是OS的一部分,Windows中到處到有它的影子。(事實上,看一下Windows 98 的CAB檔案就知道,這個肥大的東西就藏在在win98_62.cab檔案中,所以說它是Windows的一部分。)自從有了COM以後,MFC越來越成問題:其嵌套類的使用完全失去了個性。稍後還要詳細描述這方面的問題。
    那麼到底使用什麼呢?最終,我采各家所長建立了自己的系統,同時使用MFC和ATL。這樣就成全了我的工作,並充分利用了MFC和ATL各自的優點,MFC和ATL之間達到了取長補短的效果。COMToys利用MFC的類工廠和IUnknown,但繞開了嵌套類。同時還利用了ATL的智能指標,註冊器和多繼承,而過濾出了厚重的模板及易造成混淆的物件模型。這種混合方法不僅實用,而且還很發燒(加上是自己寫代碼,從而總是能更好地理解代碼)。
    我並不是說COMToys怎麼怎麼好,只是提供一種思路和想法,希望能拋磚引玉。主要目的是展示完全不必專門使用MFC和ATL,或其它系統就可以編寫自己的COM對象建立平台。告訴你如何通過建立一個基本結構架構,然後用C++輕鬆進行COM編程——甚至比Visual Basic還容易!

粗糙的嵌套類

    這是專家的評價,讓我們看看代碼。為瞭解釋COMToys的工作原理,我要做的第一件事情是說明COMToys被設計用來解決的問題之一:避開MFC使用的嵌套類。為此讓我們先簡單回顧一下MFC/COM編程的基本概念。
    為了用MFC寫一個COM,要從CCmdTarget派生自己的類並使用宏實現自己的介面。例如,如果你的類實現IPersistFile,那下面是必須要寫的代碼:

// 在標頭檔中 class MyComClass : public CCmdTarget {   BEGIN_INTERFACE_PART(PersistFile, IPersistFile)   STDMETHODIMP GetClassID(LPCLSID pClsID);   STDMETHODIMP IsDirty(void);   ……   END_INTERFACE_PART(PersistFile) };     

這些宏在主類中聲明一個嵌套類:MyComClass::XPersistFile,並聲明一個執行個體:m_xPersistFile。MyComClass::XPersistFile實現IPersistFile的方法,包括從IUnknown繼承的方法。為了讓MFC知道這個介面,必須建立一個介面映射:

// 在 .cpp 檔案中BEGIN_INTERFACE_MAP(CMyComClass, CCmdTarget)   INTERFACE_PART(CMyComClass, IID_IPersistFile, PersistFile) ……END_INTERFACE_MAP()      

這個宏為你的類中每個COM介面產生一個細目表。每一個細目表的表項儲存介面的IID以及在實現它的嵌套類主類中的位移量

 { &IID_IPersistFile, offsetof(CMyComClass, m_xPersistFile) }      

一旦你聲明並實現了這個介面映射,就必須實現它們的方法,包括AddRef,Release,和QueryInterface。因為這個類是嵌套的,必須為COM對象支援的每一個介面編寫IUnknown,即使實現都相同。例如:

// AdRef 和Release的代碼相同 STDMETHODIMP CMyComClass::XPersistFile::QueryInterface(...) {   METHOD_PROLOGUE(CMyComClass, PersistFile)   return pThis->ExternalQueryInterface(...); }      

    METHOD_PROLOGUE在MFC中是必不可少的,用以獲得它們之間的關係(在任何DLL的進入點,AFX_MANAGE_STATE是不可缺少的)並設定pThis,它指向父類CMyComClass("this"指標地址減去嵌套的位移量)。CCmdTarget::ExternalQueryInterface搜尋你的介面映射尋找與請求的介面匹配的IID條目。如果找到,則將位移量添加到這個指標並返回結果——如果一切正常,它指向實現介面的嵌套對象。這種方法能行得通,但是極其麻煩。 首先,在實現每一個介面的IUnknown時就非常的彆扭,它開啟了出錯的大門,而且因為複製不必要的代碼而變得臃腫不堪。 第二,你不能從介面方法中直接存取你的類成員;而是必須通過pThis來存取它們——這簡直就是故弄玄虛;從概念上講,介面方法屬於外部類,所以為什麼不能像訪問其它成員那樣訪問它的成員呢? 最後,也是最讓人討厭的一點,嵌套類方式無法讓你在衍生類別中重載介面方法。假設你派生一個新類,CMyComClass2,並且你只想重載IPersistFile::SaveCompleted以便設定一個標誌,ON_UPDATE_COMMAND_UI處理器將檢查這個標誌並顯示"正在儲存….",直到完成儲存。你不用做什麼。CMyComClass沒有可重載的SaveCompleted函數。實現SaveCompleted的類被嵌套在其中,CMyComClass::XPersistFile,沒有辦法重載它的方法。除了要重載SaveCompleted以外,你還必須在衍生類別中重新實現整個IPersistFile介面,建立另一個嵌套類,其中包含什麼也不做的方法。只是調用一下基類方法,CMyComClass::m_xPersistFile。 我在前面文章的代碼中碰到了這個問題。當容器設定現場時,為了讓CBandObj衍生類別做些事情,我不得不提供一個新的虛函數,CBandObj::OnSetSite,並從CBandObj::XDeskBand::SetSite中調用它。其它介面方法如何呢?我是不是要為CBandObj實現的每一個介面方法引入類似OnXxx的東西?我不想這樣! 出於這些原因,許多C++程式員——包括ATL代碼編寫者——都採用多繼承進行COM編程。從每個實現的介面派生多個自己的COM類。

class CMyComClass :    public IPersistFile,   public IContextMenu, ...{   // IUnknown   STDMETHOD_(ULONG, AddRef)();…… // IPersistFile   STDMETHODIMP GetClassID(LPCLSID pClsID);…… // IContextMenu    STDMETHOD (QueryContextMenu)(...);……};    

    所有方法屬於主類,因此你可以用通常的方式重載它們,而且所有的方法都能直接存取類成員;不需要使用pThis。然後利用C++的魔力,只要實現一次AddRef,Release和 QueryInterface即可,並且同樣的實現適用於所有的IUnknown執行個體。這恰恰是因為C++的規則使然,"純粹的虛函數總是通過實現它的任何子類進行重定義的。"所有虛表(vtbls)都將有IUnknown的位置,在這個位置指向相同的物理函數。這對於其它可能被多繼承的介面也一樣;例如,IPersist從IPersistFile 和IPersistStream繼承而來。十七所示,我準備稱它為魔力MI法則。


圖十七 多繼承 

    既然多繼承如此之好,為什麼MFC不用它呢?因為一碰到具體的類——那些有真實函數和資料的類時——多繼承便引起混淆。如果你寫x = m_foo,這裡的 m_foo是個從A繼承還是從B繼承的呢?而且MFC從CObject派生了它的所有類,用多繼承會導致可怕的菱形層次。雖然可以用虛基類來克服這種不足,但事情會更糟。所以建立MFC的那位哥們兒聰明地決定避開多繼承。(待續)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.