?? 205.htm
字號:
HRESULT Submit();}</span><span id=Layer51></pre></font></div><p><font size=2 color=#3c3c3c face=arial>使用IDL是定義一個COM介面最有彈性的方式,不過它也同樣是最復雜的。通常使用的人都是C++的程式設計師,他們都是能忍受痛苦的中間份子。然而Visual Basic的門檻比較低,它也允許定義COM介面不須要學習IDL,除非你希望控制介面定義的每一個部份。因為IDL并不是個簡單的語言,所以這也算是一件好事。</span><span id=Layer52></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>用其他方式來定義介面也是行得通的</span><span id=Layer53></font></p><hr><p><font size=2 color=#3c3c3c face=arial>不管它們是如何定義的,COM介面共享相同的特色:一旦定義就是永久。換句話說,一旦介面在這個世界上出現之後,只要它有了客戶端,COM的規則禁止你對這介面進行任何的變動。當然在開發的過程中,介面是可以進行改變的,不過一旦被使用後,就不能夠再更改。到目前為止,被當做特定介面IID的GUID將永遠用來辨識這個介面。若新增一個新的method,或新增一個新參數,則需定義一個全新的介面,與新的IID(不會有定義舊介面的新版本這種情況發生)。一開始你可能會覺得這個規則很愚蠢,不過它似乎已成為COM版本控制的中心要素,這也是本章稍後要談論的。</span><span id=Layer54></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>一旦COM介面被使用時,就無法再改變了</span><span id=Layer55></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 介面繼承</span><span id=Layer56> </b></font>COM允許介面之間相互繼承。這代表了不須要再明確地重復定義既存的介面以成為新介面,新介面的定義者可以參考既有的介面,而它的method就會自動地包含進來了。這樣將會帶來很大便利性,我們將會看到,這也是一種很基本的案例。</span><span id=Layer57></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>新的COM介面可以從既有的介面繼承下來</span><span id=Layer58></font></p><hr><p><font size=2 color=#3c3c3c face=arial>COM只支援單一繼承,這代表的含意是一個介面一次只能從一個介面繼承下來。同樣地,只能繼承此介面的method定義,而不是繼承這個介面method真正實作的部份。這代表COM并不支援實作繼承。</span><span id=Layer59></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>COM只允許單一繼承</span><span id=Layer60></font></p><hr><p><font size=2 color=#3c3c3c face=arial>當一個程式設計師撰寫一個繼承自其它介面的程式碼時,這個程式設計師必須提供所有介面上所有method的程式碼。當然,內建實作繼承的語言,如C++可用來達到這個目的,但COM本身仍舊不支援method真正實作的繼承。不管是好是壞,Visual Basic,Microsoft平臺上最廣泛使用的語言,提供有限的COM介面繼承能力。這個事實大幅影響這個工具未來的發展性。</span><span id=Layer61></font></p><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 屬性</span><span id=Layer62> </b></font>COM物件顯露的介面包含method,再也沒其它東西了。不過某些程式開發語言,尤其是Visual Basic,廣泛地依賴屬性的使用。一個屬性是一個物件能包含的值,能夠進行讀取或是修改的動作。舉例來說,一個代表訂單的COM物件正要被提交了,它可能包含一些屬性,如Customer與Order Number。若想要在Visual Basic的環境中運作良好,COM物件必須能夠支援這個主意。當然,它們是可以的。</span><span id=Layer63></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>Visual Basic廣泛地使用屬性</span><span id=Layer64></font></p><hr><p><font size=2 color=#3c3c3c face=arial>實作在COM中某個介面的一個可讀取屬性,可透過一個method存取屬性的值。若這個屬性也可以進行修改,則介面將會包含其它的method允許進行修改的動作。從COM物件的觀點來看,一個屬性就是指它包含的資料,它的值可透過呼叫適當的method來讀取或修改,這沒什麼特別的。不過允許使用Visual Basic撰寫的客戶端透過自己原生的屬性來檢視這些method,會讓Visual Basic的程式設計師生活更為容易了。</span><span id=Layer65></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>一個COM物件也可以擁有屬性</span><span id=Layer66></font></p><hr><p><font size=2 color=#3c3c3c face=arial>在今天,常常看到以程式語言專屬的method與屬性來描述COM物件,特別在給Visual Basic程式設計師看的文件上。有些甚至支援COM IDL以定義method來讀取及設定屬性的值。雖然有些號稱純粹派的藝術家可能會抱怨,但在COM中,Visual Basic導向屬性的存在,實際上是另一種挑戰,尤其在建立系統物件模型時,它能夠有效地在許多程式語言中使用。</span><span id=Layer67></font></p><font color=#3e72d7 face=arial size=4><b>類別(Class)</span><span id=Layer68></b></font><p><font size=2 color=#3c3c3c face=arial>在物件的技術中,一個類別通常被視為一個范本,以建立許多相似的實例。舉例來說,Bridge類別可能有很多實例,如Golden Gate、Brooklyn與Pont Neuf。COM中的類別就很像這個。一個COM類別是由一些程式碼實作出來的,可用來建立一個或多個COM物件的實例,每一個COM物件的類別都是相同的。</span><span id=Layer69></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>每個COM物件都是某些類別建立的</span><span id=Layer70></font></p><hr><p><font size=2 color=#3c3c3c face=arial>大部份COM類別都會指派一個類別識別碼,通常寫成CLSID。一臺電腦上可供一般使用的COM類別與類別的CLSID都列示在機器上的系統登錄。有了CLSID,便可查看系統登錄項目以找尋包含實作這個類別的程式碼檔案所在的位置。</span><span id=Layer71></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>一個COM類別通常會被指派一個CLSID</span><span id=Layer72></font></p><hr><p><font size=2 color=#3c3c3c face=arial>CLSID就是GUID,因此不會有兩個類別擁有相同的名稱這種情況發生。讓每個類別都擁有唯一的名稱是很重要的,因為就我們所視,任何公司建立的類別可能存在同一臺機器上,或在一個公用分散的環境中。若這些類別以簡單的字元名稱來命名,則兩個完全不同的類別,它們的名稱就可能會相同。因為建立類別的許多實例時,命名沖突可能會產生問題。我們將於下一節中描述。</span><span id=Layer73></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>CLSID就是GUID,它代表它們是全域唯一的</span><span id=Layer74></font></p><hr><p><font size=2 color=#3c3c3c face=arial>同時,對程式設計師來說,使用CLSID并不是很方便的,因為16位元組的GUID并不親切。為了簡化這個問題,COM同樣也允許為每個類別指定一個程式識別碼,較常被稱呼為ProgID。ProgID只是字元字串,因此程式設計師可以很容易使用、記憶。同時在必要時,它們也可以轉換為CLSID。這個轉換動作依賴於系統登錄,它包含了安裝在這臺電腦上所有的COM類別其ProgID到CLSID的對照表。ProgID并不能確保一定是唯一的,不過通常都會使用命名原則,不讓沖突的情況發生。(如所有的ProgID都以公司的商標開始命名)在任何一種情況下,ProgID只是為了方便而使用的,一個類別真正的名字是它的CLSID。</span><span id=Layer75></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>一個類別也可以以人類可讀取的ProgID命名</span><span id=Layer76></font></p><hr><p><font size=2 color=#3c3c3c face=arial>特定COM類別的物件一般都會實作一些特殊的介面。舉例來說,以CLSID_X 識別的COM類別之所有物件都可能實作IOrderEntry與IOrderStatistics介面。然而隨著時間消逝,更改特定COM類別的物件所支援的介面是合法的,你可以新增一個介面,或丟棄一個舊介面。這類的變動并不需要更改CLSID。因此一個COM類別實際上是指一組特定的程式碼,必要的動作只是更改程式碼所要做的事。當某類別的實例是由修改過的程式碼而建立時,這個實例將會反應出目前實作的功能。</span><span id=Layer77></font></p><p><font size=2 color=#3c3c3c face=arial>實作一個COM類別的程式碼可以包裝成一個單獨的執行檔,通常稱為 EXE檔案,或者包裝成一個動態連結程式庫(DLL)。這兩者在目前都很常見,雖然因為某些在本書討論到的理由,以及COM+的到來,使得將類別制作成DLL較為吸引人。實作成EXE的COM物件永遠執行在與客戶端不同的行程中。然而實作成DLL的COM物件無法執行在自己的行程中。這類的物件將會執行在與它客戶端相同的行程中,或者藉由「代理程序(surrogate)」的協助,執行在其它的行程中。</span><span id=Layer78></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>COM類別的程式碼可以是一個DLL或一個EXE</span><span id=Layer79></font></p><hr><p><font size=2 color=#3c3c3c face=arial>一個實作成DLL的COM類別稱為一個in-process(或只稱in-proc)伺服器,而實作成EXE的COM類別,若它執行在與客戶端相同的機器,則稱作本機伺服器(local server);若執行在其它的機器上,則稱為遠端伺服器(remote server)。總共有叁種可能性,圖5-2展示QwickBank網域中使用兩臺電腦的情形。在此還有一個術語很重要,哪一個是元件。在此我得說明一下,在軟體世界中并沒有一個廣為使用的字眼以定義之,也無法讓人認同,但在COM中,一個元件就是一個檔案,可能是一個DLL或是一個EXE,它包含一個或多個COM類別的程式碼。實際上你可以把「元件」這個字與「伺服器」劃上等號。目前并沒有什麼花俏或抽象的定義,同時它也未涵蓋在我們的工業中所有的術語,不過對COM來說,它是很精確的。</span><span id=Layer80></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>COM元件可以執行在與客戶端相同的行程,或另外的行程中</span><span id=Layer81></font></p><hr><hr><font face=Arial Black color=#3e77d7 size=3><b>附注 </b></font><p><font size=2 color=#3c3c3c face=arial>雖然圖中并沒有顯示,執行在一個代理行程中的DLL同樣可以透過遠端存取。就</span><span id=Layer82> <a href=208.htm# target=_new>第八章</span><span id=Layer83></a> 所描述,這在COM+中非常重要。</span><span id=Layer84></font></p><hr><br><center><a target=_new href=imagesh/5-2.gif><img border=0 src='imagesl/5-2.gif'></a></center></span><span id=Layer85><center><table border=0 ><td align=center><font color=#3c3c3c face=arial size=2><font size=2 face=arial color=#3e80d7><b> 圖 5-2</span><span id=Layer86> </b></font>COM物件可以是in-proc、本機或遠端伺服器。</span><span id=Layer87></td></table></font></center><font color=#3e72d7 face=arial size=4><b>建立物件實例</span><span id=Layer88></b></font><p><font size=2 color=#3c3c3c face=arial>在典型的物件導向語言中,程式設計師定義一個類別,然後在執行時期,使用某些語言內建的操作來建立類別的實例。舉例來說,在C++中呼叫new運算子再加上類別的名稱,將會建立此類別的新實例。COM是一個系統物件模型,而不是一個語言專屬的方案,不過它同樣允許客戶端呼叫適當的函數建立COM類別的實例 COM物件。這些函數的長相則依賴使用的程式語言不同而不同,因為每個語言都有自己與COM的對照。我將從C++程式設計師的觀點來討論COM物件如何建立,然後再從Visual Basic或Java程式設計師的觀點來探討之。</span><span id=Layer89></font></p><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 物件建立的基本概念</span><span id=Layer90> </b></font>若要建立特定COM類別的新實例,一個C++的程式設計師可以呼叫兩個標準的函數:CoCreateInstance 或 CoCreateInstanceEx。這兩者都是由COM runtime函式庫實作的,更常被稱為runtime,它是Windows 2000與Microsoft其它作業系統標準的一部份。CoCreateInstance與CoCreateInstanceEx兩者都可以被視為C++ New運算子的同義字。雖然CoCreateInstanceEx可以在單機使用,但經證明它和DCOM一起使用時特別有用,我們將在稍後描述。因此在此我們只使用CoCreateInstance。</span><span id=Layer91></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>一個C++客戶端可以呼叫CoCreateIns-tance來建立一個COM類別的新實例</span><span id=Layer92></font></p><hr><p><font size=2 color=#3c3c3c face=arial>當一個客戶端呼叫CoCreateInstance,它傳入許多的參數,最有趣的部份展示於圖5-3步驟一。客戶端最重要的便是傳入一個CLSID,指明欲建立新實例的類別。客戶端也會傳入一個IID,指明新建立的物件之第一個想要使用的介面指標。</span><span id=Layer93></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>CoCreateInstan-ce在COM+目錄中查看特定的類別</span><span id=Layer94></font></p><hr><p><font size=2 color=#3c3c3c face=arial>當這個呼叫進行後,COM runtime以這個特定的CLSID搜尋本機的COM+目錄,如圖5-3的步驟二。COM+目錄是一個邏輯上的項目,它實際上包含兩個不一樣的東西:系統登錄與一個資料庫,稱RegDB,它和COM+都是Windows 2000新增的。包含在COM類別的所有元件只要是能夠使用CoCreateInstance來建立的都會列在系統登錄中,同時所有已設定元件(configured component) 的許多屬性都會儲存在RegDB。</span><span id=Layer95> <a href=208.htm# target=_new>第八章</span><span id=Layer96></a> 中將會描述,一個已設定元件可以使用COM runtime提供的額外服務。然而在本章中,我們關心的是這些被稱做未設定元件(unconfigured component)的東西。在Windows 2000發行之前,只有一種可能的COM元件,當然程式設計師仍有建立這類傳統COM軟體的自由。RegDB并沒有儲存關於未設定元件的資訊,這些資訊都是儲存在系統登錄之中。因此圖5-3中顯示,CLSID_Y并沒有RegDB項目。</span><span id=Layer97></font></p><p><font size=2 color=#3c3c3c face=arial>接下來COM runtime粹取出儲存在系統登錄中CLSID_Y的檔案名稱,在這個案例下是CLSID_Y。這個檔案中包含實作這個類別的程式碼,因此COM runtime將載入這個檔案。一旦完成這個動作,COM runtime就會引導這個新載入的程式碼建立特定類別的實例,如圖5-3步驟叁所示。然後新建立的物件便會將介面指標回傳,這個介面就是客戶端在CoCreateInstance呼叫時傳入的IID指明的,如步驟四所示。最後runtime將把這個介面指標回傳到客戶端,然後客戶端便呼叫新物件上的method,如步驟五所示。</span><span id=Layer98></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>物件建立後,便回傳一個介面指標到客戶端</span><span id=Layer99></font></p><hr><br><center><a target=_new href=imagesh/5-3.gif><img border=0 src='imagesl/5-3.gif'></a></center></span><span id=Layer100><center><table border=0 ><td align=center><font color=#3c3c3c face=arial size=2><font size=2 face=arial color=#3e80d7><b> 圖5-3</span><span id=Layer101> </b></font>一個客戶端可以呼叫CoCreateInstance來建立COM類別的實例。</span><span id=Layer102></td></table></font></center><p><font size=2 color=#3c3c3c face=arial>很可能這個基本的結構會有許多的變形。舉例來說,若所要求的類別之程式碼載入後,COM可以允許物件建立的要求由這個執行中的復本掌控,并非每個要求都需要重新載入這個元件。同樣地,在Windows 2000中,建立任何的COM物件同樣會建立這個物件的context。Context對於已設定元件特別有用,因此我們將在</span><span id=Layer103> <a href=208.htm# target=_new>第八章</span><span id=Layer104></a> 中討論。</span><span id=Layer105></font></p><p><font size=2 color=#3c3c3c face=arial>在Visual Basic與Java中,物件建立的過程就和C++中的一樣,最後呼叫CoCreateInstance,搜尋COM+目錄等等,但程式設計師看起來有點不同。在Visual Basic中,客戶端可以呼叫CreateObject函數,而不必呼叫CoCreateInstance,傳入一個ProgID而非一個CLSID。Visual Basic runtime系統會使用系統登錄把ProgID轉換成CLSID,然後執行一些基本上相同的處理過程,如圖5-3所示。一個Visual Basic的客戶端也可以使用Visual Basic的NEW運算子來建立一個COM物件。若要達到這個目的,Visual Basic的程式設計師可以宣告一個適當的變數,然後將這個變數的值指派成新呼叫的結果。回傳哪一個物件的介面指標則由這個變數的類型而決定。</span><span id=Layer106></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>Visual Basic客戶端可以使用CreateObject或new建立COM物件</span><span id=Layer107></font></p><hr><p><font size=2 color=#3c3c3c face=arial>在Java中事情甚至變得更容易了。程式設計師只要永遠呼叫Java的New運算子,沒有特殊的COM物件建立函數。因為Java對物件的看法與COM相當雷同,這兩者都允許物件擁有多個獨立的介面,舉例來說,Microsoft JVM能對照標準Java語言的語法以便與COM物件一起運作,或呼叫COM物件。</span><span id=Layer108></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>Java程式設計師可以呼叫New來建立一個COM物件</span><span id=Layer109></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> Class Factory</span><span id=Layer110> </b></font>當一個客戶端呼叫CoCreateInstance (或其中一個對應到Visual Basic或Java的語法,最後都會變成同樣的東西),如前所描述,COM runtime會建立某個COM類別的實例被建立。對於客戶端來說,如何發生這件事是個魔術 它只是發出要求,然後物件就存在了。然而對於撰寫這個COM類別程式碼的人,它可能不是個魔術。程式設計師可能得明確地實作一個Class Factory。</span><span id=Layer111></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>物件的建立通常都是依賴於Class Factor</span><span id=Layer112></font></p><hr><p><font size=2 color=#3c3c3c face=arial>一個Class Factory是一個COM物件實作了標準的IClassFactory介面。當一個COM runtime建立一個COM類別的新實例時, 實際上它呼叫了IClassFactory介面上一個叫 CreateInstance的method。C++的客戶端也有可能要求這個介面的指標,然後直接建立新實例,不過這個選項目前不適用於Visual Basic或Java的程式設計師。</span><span id=Layer113></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>每個Class Factory物件都實作了IClassFactory 介面</span><span id=Layer114></font></p><hr><p><font size=2 color=#3c3c3c face=arial>一個在C++建立COM類別的程式設計師,可以撰寫程式碼手動取得所要求的Class Factory。C++總是提供給COM程式設計師最大的權力與痛苦。或者C++的程式設計師也可以使用工具,如ATL,提供Class Factory標準的實作。若說這是在今天主要C++程式設計師的第二條路真是一點也不為過,因為撰寫Class Factory并不是特別有趣的東西。使用Visual Basic或Java的程式設計師從來就不需要煩惱Class Factory這些語言的執行時期環境會在它們需要時自動地提供。然而,不管Class Factory是由何而來,它扮演著建立COM物件基本要素的角色。</span><span id=Layer115></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>程式設計師很少需要自行撰寫Class Factory</span><span id=Layer116></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 在一個物件模型中建立物件</span><span id=Layer117> </b></font>使用CoCreateInstance 建立客戶端需要的每個COM物件是太過夸張了。有時,若只是要建立一個物件,使用標準的物件建立函數是很簡單的,然後讓物件提供自己的機制以建立相同類別或不同類別的物件。當透過這種方式使用COM物件時,有時稱這種模型為物件模型。</span><span id=Layer118></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>并非所有的物件都是使用CoCreateIns-tance建立的</span><span id=Layer119></font></p><hr><p><font size=2 color=#3c3c3c face=arial>舉例來說,Microsoft Excel提供給使用者的服務,同樣也可以讓其它的應用程式來存取。為了達到這的目的,Excel實作一組不同的COM類別,每個類別提供某些method。不過一個客戶端使用CoCreateInstance(或對照到Visual Basic或Java的語法)只建立一個物件的實例。這個物件的method可以將介面指標回傳到其它由不同類別新建立的COM物件。這些物件的建立并不依賴於系統登錄,這些類別也沒有CLSID的必要。這個物件模型定義成一個階層狀,樹狀結構中下層的物件都是由較高階層的物件建立的。客戶端仍舊握住根物件下物件直接的介面指標,不過牽涉到物件建立過程的系統登錄只有根物件的而已。運作的過程如圖5-4。</span><span id=Layer120></font></p><br><center><a target=_new href=imagesh/5-4.gif><img border=0 src='imagesl/5-4.gif'></a></center></span><span id=Layer121><center><table border=0 ><td align=center><font color=#3c3c3c face=arial size=2><font size=2 face=arial color=#3e80d7><b> 圖5-4</span><span id=Layer122> </b></font>在某些情況下,客戶端使用CoCreateInstance來建立COM物件階層中的根物件,然後使用物件自己的method來建立其它的物件。</span><span id=Layer123></td></table></font></center><hr><font face=Arial Black color=#3e77d7 size=3><b>附注 </b></font><p><font size=2 color=#3c3c3c face=arial>這些method都是透過dispinterface顯露的。只要在Visual Basic或其它語言中撰寫簡單的程式,使用內嵌在Excel中的COM物件,就有可能自動處理某些工作,而不必手動進行。這是早期dispinterface的其中一種應用,這也就是為什麼有時稱做自動化(automation)的原因了。</span><span id=Layer124></font></p><hr><font color=#3e72d7 face=arial size=4><b>全域的介面: IUnknown</span><span id=Layer125></b></font><p><font size=2 color=#3c3c3c face=arial>還有許多重要的問題尚未回答呢。首先,我們看到當物件建立時,客戶端取得新建立的物件之介面指標。不過因為這個COM物件總是支援多重介面,那麼客戶端如何取得物件上其它介面的介面指標呢?第二,我已描述COM物件是如何建立的,不過它們是如何被毀滅的呢?因為COM在今日已廣被使用,也因為我們機器的記憶體中并沒有充滿舊的COM物件,一定有某種讓它們消失的機制。</span><span id=Layer126></font></p><p><font size=2 color=#3c3c3c face=arial>這些問題都可以透過IUnknown中的method解決。IUnknown是COM中最基本的介面。每個COM物件都必須支援IUnknown,它也是為何所有的COM物件實際上支援兩個或多個介面的原因:一個只提供IUnknown介面的物件并沒有多大的用處。實際上,每個COM物件上的每個介面都必須繼承自IUnknown,它代表只要有任何COM物件的介面指標,客戶端永遠都可以呼叫IUnknown中的method。當然就如它的重要性,這個介面中只有叁個method:QueryInterface、AddRef,與Release。接下來將討論這叁者中的第一個method允許客戶端要求一個COM物件的新介面,而最後一個則是用來控制COM物件的生命周期的。</span><span id=Layer127></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>每個COM物件的每個介面都繼承自IUnknown</span><span id=Layer128></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 取得一個物件的新介面指標</span><span id=Layer129> </b></font>當物件建立時,客戶端可以取得新物件的第一個介面指標。一旦取得這個指標,它便可以呼叫物件的IUnknown::QueryInterface指標來取得這個物件可能支援的其它介面。圖5-5展示這個運作的過程。</span><span id=Layer130></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>QueryInterface可讓你從物件獲得新的介面指標</span><span id=Layer131></font></p><hr><br><center><a target=_new href=imagesh/5-5.gif><img border=0 src='imagesl/5-5.gif'></a></center></span><span id=Layer132><center><table border=0 ><td align=center><font color=#3c3c3c face=arial size=2><font size=2 face=arial color=#3e80d7><b> 圖5-5</span><span id=Layer133> </b></font>客戶端呼叫IUnknown的QueryInterface method以獲得一個COM物件上的新介面指標</span><span id=Layer134></td></table></font></center><p><font size=2 color=#3c3c3c face=arial>回顧一下每個介面都繼承自IUnknown,如此代表所有IUnknown的method都可以在任何介面指標上被呼叫。當一個客戶端呼叫QueryInterface,它會傳入介面的IID。若COM物件實作了這個特定的介面,QueryInterface的呼叫將會回傳一個指向這個介面的介面指標。若物件并沒有實作這個介面,QueryInterface就會回傳Null。</span><span id=Layer135></font></p><p><font size=2 color=#3c3c3c face=arial>跟往常一樣,這個呼叫看起來的樣子是依賴於用來撰寫客戶端的程式語言。一個C++的客戶端可以直接呼叫QueryInterface。然而,Visual Basic與Java客戶端則不行。這兩個語言取代的做法是在必要時,在幕後呼叫這個method。不變的是,COM在任一種情況下都是一樣的。不同之處在於,COM的機制要如何對應到每一個程式開發語言。</span><span id=Layer136></font></p><p><font size=2 color=#3c3c3c face=arial>QueryInterface是一個非常簡單的method。僅管它有簡單性,但它是構成COM處理物件版本的基礎。因為它允許客戶端在呼叫一個物件介面上的method之前,先安全地判斷物件是否支援某個介面,客戶端可以使用舊版本的物件,使用這個支援較少介面的物件,而不會發生問題。客戶端可能從舊物件接收到比實際上要少一些的服務,同時并不強制客戶端呼叫特定的method(或許會毀損)來判斷物件是否支援之。</span><span id=Layer137></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>QueryInterface對版本控制來說是很重要的</span><span id=Layer138></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> COM物件如何被毀滅</span><span id=Layer139> </b></font>因為客戶端明確地建立物件,因此必須有某種方法可以毀滅它們。某些程式語言,如C++,一旦物件建立後,允許客戶端明確地刪除物件。然而,COM的方式則有所不同。在COM中并沒有標準的機制可允許客戶端直接毀滅一個物件。取代的做法,COM物件的生命周期是由一項技術所控制的,稱參考計數(reference counting)。</span><span id=Layer140></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>COM依賴參考計數</span><span id=Layer141></font></p><hr><p><font size=2 color=#3c3c3c face=arial>這個概念很簡單:每個COM物件會追蹤它擁有多少個介面指標,然後將這個值儲存在它的參考計數中。當一個客戶端第一次建立一個COM物件,物件將會擁有一個介面指標,因此參考計數中的值將會是一。若這個客戶端使用QueryInterface取得這個物件的其它介面指標,物件便會增加它的參考計數值為二。當一個客戶端使用完介面指標後,它必須在這個介面指標上呼叫IUnknown::Release。當它接收到這個呼叫時,物件便從它的參考計數中減去一。一旦客戶端在它握住的此物件之所有介面指標上呼叫了IUnkown::Release,則物件的參考計數值便變成零。當這個情況發生時,通常物件會自我毀滅。</span><span id=Layer142></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>當使用完COM物件後,客戶端必須呼叫Release</span><span id=Layer143></font></p><hr><p><font size=2 color=#3c3c3c face=arial>然而對於物件的客戶端來說,將一個介面指標傳到其它的客戶端是完全合法的。若沒有任何人告訴物件發生什麼事,它的參考計數將不會正確地反應出它使用的介面指標數目。此外,每當介面指標被復制後,例如當指標被傳送到其它客戶端時,則必須呼叫物件的AddRef method。如此將會讓物件將參考計數的值增加一,正確地為新的介面指標負起責任。同時,這個物件使用完畢後,新介面指標的使用者必須呼叫Release。</span><span id=Layer144></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>當一個介面指標被復制時,客戶端必須呼叫AddRef</span><span id=Layer145></font></p><hr><p><font size=2 color=#3c3c3c face=arial>參考計數是個好主意,也能夠聰明地控制COM物件的生命周期,特別是在沒有一個客戶端可以可靠地知道何時摧毀物件是安全的這種環境中。對於使用C++撰寫的客戶端,程式設計師必須正確地撰寫呼叫Release與AddRef的程式碼。對於使用Visual Basic 與Java撰寫的客戶端而言,這些呼叫的動作是不必要的。這兩個程式語言的runtime環境會在必要時呼叫Release與AddRef。是的,對非C++的程式設計師而言,生活是較為簡單的。</span><span id=Layer146></font></p><font color=#3e72d7 face=arial size=4><b>呼叫Method</span><span id=Layer147></b></font><p><font size=2 color=#3c3c3c face=arial>在預設情況下,呼叫COM物件上的method是同步的操作。換句話說,客戶端發出呼叫,執行method,然後這個呼叫便回傳了,客戶端必須有耐心地等待整個過程。直到呼叫回傳為止,呼叫這個method的客戶端執行緒是被綁死的。</span><span id=Layer148></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>大部份的me-thod都是以同步的方式呼叫</span><span id=Layer149></font></p><hr><p><font size=2 color=#3c3c3c face=arial>在Windows 2000,COM客戶端也可以以非同步方式呼叫method。非同步呼叫并不會在客戶端等待回應時被綁死不能往下執行,而是這個呼叫會馬上回傳。當這個呼叫在執行時,允許客戶端繼續執行它的工作。雖然對程式設計師而言這較為復雜,但是這種呼叫的風格有時會顯著地增進執行效能。不管是非同步或使用傳統的風格進行,客戶端也可以取消進行中的呼叫。本節將描述這兩種選項,先從取消開始吧。</span><span id=Layer150></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>你可以以同步或非同步方式進行呼叫,或取消呼叫</span><span id=Layer151></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 取消呼叫</span><span id=Layer152> </b></font>假設一個客戶端對某些COM物件發出一個非同步的呼叫。特別是這個物件是執行在其它的行程或其它機器上,這個呼叫有可能會花費比客戶端預期等待還要久的時間執行。類似這樣的情況,若有某些方式可以取消進行中的呼叫是很有用的。</span><span id=Layer153></font></p><p><font size=2 color=#3c3c3c face=arial>Windows 2000增加一個很簡單的機制以達到這個目的。(每當某些執行緒發出一個非同步呼叫時,便建立一個Cancel物件,此物件實作了ICancelMethodCalls介面。若要取消這個呼叫,客戶端的第二條執行緒便可以呼叫CoGetCancelObject,傳入發出呼叫的這條執行緒之識別碼。如此將會為這個未完成的呼叫回傳一個指向Cancel 物件ICancelMethodCalls介面的介面指標。若要取消這個呼叫,第二條執行緒只要呼叫Cancel物件上的ICancelMethodCalls::Cancel。</span><span id=Layer154></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>呼叫Cancel物件上的me-thod可以取消進行中的呼叫</span><span id=Layer155></font></p><hr><hr><font face=Arial Black color=#3e77d7 size=3><b>附注 </b></font><p><font size=2 color=#3c3c3c face=arial>對於C++程式設計師來說,至少證據顯示這個機制的目標。</span><span id=Layer156></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 非同步呼叫</span><span id=Layer157> </b></font>同步呼叫本來就很容易理解,但很悲哀地,非同步呼叫就不容易了。支不支援是選擇性的,若一個COM物件接收任何非同步呼叫,它實作了ICallFactory,這個介面只包含一個method,CreateCall。當客戶端希望以非同步方式呼叫時,它呼叫目標物件上的ICallFactory::CreateCall,傳入欲呼叫的method所在之介面的介面識別碼(IID)。這個呼叫回傳一個指向Call物件之介面指標,這個物件是客戶端想要對其發出呼叫的物件。</span><span id=Layer158></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>非同步呼叫依賴一個Call物件</span><span id=Layer159></font></p><hr><p><font size=2 color=#3c3c3c face=arial>非同步呼叫是根據每個介面來定義的,介面中每一個呼叫不是非同步呼叫就是全部為同步呼叫。若要讓一個介面可以使用非同步呼叫,它的建立者必須將它標識為一個特定的IDL屬性。如此可讓Microsoft IDL (MIDL)編譯器產生這個介面的非同步定義部份,而每個介面的method將會一分為二。當客戶端希望呼叫method時,便呼叫第一個method,指派一個以「Begin_」開始的名稱。這個method包含原始method中所有的輸入參數。當客戶端希望取回method的結果時,便可以呼叫這一對method中的第二個method,指派一個以「Finish_」開始的名稱。舉例來說,回想本章提及的IOrderEntry介面。若這個介面標識為非同步,這些method的每一個都會切割成一對method。舉例來說, Add method將會切割為Begin_Add與Finish_Add。而Submit method將會切割為Begin_Submit與Finish_Submit。雖然說一個特殊的呼叫物件一次只能由一個非同步的呼叫來處理客戶端,但是在Begin method與對應的Finish method可自由地進行任何作業,它不須要等待。</span><span id=Layer160></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>非同步介面將每個method切割為二</span><span id=Layer161></font></p><hr><p><font size=2 color=#3c3c3c face=arial>對於實作非同步呼叫的物件來說,非同步method與其它的method并沒有什麼不同。一旦被呼叫後,method便執行,然後回傳一些結果。然而一旦這個呼叫回到客戶端的行程中,Begin method的proxy便會通知Call物件這個method已執行完畢。若要判斷何時非同步呼叫method的動作是否完成,客戶端可以呼叫call物件實作的ISynchronize介面上之Wait method。若這個method回傳的值指明呼叫已完成,客戶端可以呼叫適當的Finish method 取回執行的結果。如果愿意的話,客戶端可以呼叫ICancelMethodCalls介面的Cancel method以便取消執行中的非同步呼叫。</span><span id=Layer162></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>客戶端可得知非同步呼叫完成或在完成之前取消時</span><span id=Layer163></font></p><hr><p><font size=2 color=#3c3c3c face=arial>非同步呼叫有一些限制。第一,它們只能和vtable介面一起運作,在dispinterface與dual interface上的method無法使用這種方式呼叫。第二,在使用上,非同步呼叫仍舊比一般的同步呼叫要復雜些。不過它們所帶來的好處有時是值得努力的。</span><span id=Layer164></font></p><p><font size=2 color=#3c3c3c face=arial>Windows 2000同樣也提供一種或多種方式來呼叫COM物件上的method,而不須等待回應,稱做為佇列元件(Queued Component,QC)。它使用Microsoft Message Queuing (MSMQ)在客戶端與物件之間轉送要求,以及它們的參數。QC將在</span><span id=Layer165> <a href=209.htm# target=_new>第九章</span><span id=Layer166></a> 中討論。</span><span id=Layer167></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>佇列元件允許非同步呼叫</span><span id=Layer168></font></p><hr><a name=205002><font color=#3e70d7 face=arial size=5><b>執行緒與Apartment</span><span id=Layer169></b></font><p><font size=2 color=#3c3c3c face=arial>在一個簡單執行中的程式,只有一條可執行的執行緒,因此一次只有一件事發生。對於更復雜的軟體,特別是能同時讓多個客戶端存取的伺服器軟體,這個簡單方案的執行效能可能稍嫌不足。因為只有一條執行緒的含意是所有的客戶端都需等待,這樣的情況不會讓使用者快樂。較好的解決方案便是允許一個單獨的程式可以擁有多條執行緒以供執行。現在多個客戶端可以馬上被服務,每個人都會很高興。</span><span id=Layer170></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>一個伺服器行程可以包含多條執行緒,并能馬上執行</span><span id=Layer171></font></p><hr><p><font size=2 color=#3c3c3c face=arial>除了軟體的程式設計師,對每一個人來說,也就是當Windows 2000與大部份流行的作業系統提供對執行緒的支援,撰寫多執行緒的程式碼已不是件使心臟無力的事。若要讓程式設計師更容易管理執行緒,COM引進了apartment的概念。</span><span id=Layer172></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>若要讓多執行緒的伺服器變為可能,COM提供apartment</span><span id=Layer173></font></p><hr><p><font size=2 color=#3c3c3c face=arial>每個行程(process)中可以包含一或多個apartment。如圖5-6所示,任何執行中的COM物件都是執行在一個apartment中。傳統上apartment分為兩類:single-threaded apartment(STA) 與 multi-threaded apartment (MTA)。另外簡短地介紹,Windows 2000 也引進了另一個類型。一個行程中可以包含一個或多個STA,每個STA可以包含一個或多個COM物件。每個STA只有一條執行緒,這條執行緒可讓同一個apartment的所有物件共享。同時在一個特定的STA,一次只有一個物件可以執行,也就是目前使用的那個apartment的執行緒。跟STA類似,MTA也可以包含一個或多個COM物件。然而,一個行程中最多只能有一個MTA,而在MTA中每一個對物件的呼叫都發生在不同的執行緒上。因為這樣的緣故,在行程中的MTA中,單一物件的多個method可能會同時地執行。</span><span id=Layer174></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>COM提供singlethread-ed apartment(STA)與multi-threaded</span><span id=Layer175></font></p><hr><br><center><a target=_new href=imagesh/5-6.gif><img border=0 src='imagesl/5-6.gif'></a></center></span><span id=Layer176><center><table border=0 ><td align=center><font color=#3c3c3c face=arial size=2><font size=2 face=arial color=#3e80d7><b> 圖5-6</span><span id=Layer177> </b></font>一個COM物件可以執行在一個STA或一個MTA之中。</span><span id=Layer178></td></table></font></center><p><font size=2 color=#3c3c3c face=arial>對於一個實作成EXE的COM類別來說,執行所在的apartment的類型是決定於建立它的執行緒類型。在C++中,一條執行緒呼叫CoInitializeEx建立一個新的apartment,而此執行緒建立的物件預設將會執行在這個apartment中。這個呼叫上的參數決定要建立何種apartment。然而在Visual Studio 6,Visual Basic只能建立STA,因此Visual Basic的程式設計師并不需要煩惱如何控制這個選項。</span><span id=Layer179></font></p><p><font size=2 color=#3c3c3c face=arial>對於一個實作成DLL的COM類別,執行所在的apartment的類型是決定於元件儲存在系統登錄中threading model的值。若這個值設定為Apartment,則物件便在一個STA中建立,若DLL的這個值設定為Free,則會在載入它們的MTA中執行。一個DLL也可以標識成Both,代表它的物件將會建立在任何一種apartment,即使它的建立者是執行在行程的MTA中。</span><span id=Layer180></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>DLL元件擁有自己的執行緒模型,在系統登錄中設定</span><span id=Layer181></font></p><hr><p><font size=2 color=#3c3c3c face=arial>在一個STA中執行一個COM物件的其中一個好處便是設計這些物件的程式設計師不須要煩惱同步存取的問題。因為apartment中只有一條執行緒,apartment中同時只有一個物件的一個method能夠執行。這個行程整體看來還是多執行緒的,每個STA擁有自己的執行緒,但程式設計師的生活因此而大幅簡化了。STA也允許有執行緒黏著性(thread affinity)的應用程式能夠正確地運作。執行緒黏著性代表的意思是一大段的程式碼,如物件的一個method,已被指派給一條特定的執行緒或一群執行緒,這段程式碼永遠都是在這條執行緒或這群執行緒上執行。直到Visual Basic 6.0版本,每一個Visual Basic應用程式都擁有執行緒黏著性的特性,不過Visual Basic 7就沒有這個限制了。STA對展示使用者介面的物件也是很有用的,它們很適合這類的程式碼。</span><span id=Layer182></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>Singlethread-ed apartment可以保護物件同步存取的問題</span><span id=Layer183></font></p><hr><p><font size=2 color=#3c3c3c face=arial>在MTA執行COM物件的最大好處便是真正多執行緒的物件能夠提供較佳的效能。能在MTA中執行的物件很顯然地在建立、除錯上比在STA執行的物件更為復雜。這是因為欲建立它們的程式設計師必須撰寫程式允許多個客戶端能夠安全地存取這些物件。</span><span id=Layer184></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>Multi-threaded apartment允許同步存取,對程式設計師來說成本與復雜性便提高了</span><span id=Layer185></font></p><hr><p><font size=2 color=#3c3c3c face=arial>現在你已對apartment有基本的概念了,也是揭諸事實的時候:在Windows 2000中,事情比前版本的少多了。主要的原因是因為COM+重新定義了apartment的角色,而以context來論之,并允許另一個新選擇,稱Neutral apartment(也稱為Thread-neutral apartment,或TNA)。許多關於既存的COM程式碼依賴apartment來處理同步與執行緒黏著性的問題,這些東西已經有些不同了。COM+在這個領域帶來的改變將在</span><span id=Layer186> <a href=208.htm# target=_new>第八章</span><span id=Layer187></a> 中更完整地描述。</span><span id=Layer188></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>COM+的到來使得傳統的apartment變得不重要了</span><span id=Layer189></font></p><hr><a name=205003><font color=#3e70d7 face=arial size=5><b>Marshaling</span><span id=Layer190></b></font><p><font size=2 color=#3c3c3c face=arial>思考一下,當一個客戶端呼叫COM物件的method時會發生何事?再者,客戶端與物件可能在不同的apartment、行程或機器上
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -