• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            鐵觀音

            C++編程寶典

               ::  ::  ::  ::  :: 管理 ::
              1 隨筆 :: 19 文章 :: 0 評論 :: 0 Trackbacks
            [ 原創(chuàng)文檔 本文適合中級讀者 已閱讀15760次 ]

            超酷代碼:來自 COM 經(jīng)驗的八個教訓(xùn)

            原著:Jeff Prosise


            本文轉(zhuǎn)載自微軟中國社區(qū):http://www.microsoft.com/china/MSDN/library/windev/COMponentdev/CDwickedtoc.mspx

            發(fā)布日期:5/20/2004
            更新日期:5/20/2004

            原文出處:Wicked Code: Eight Lessons from the COM School of Hard Knocks (Jeff Prosise)

              在日常工作中,我看到過許多由不同開發(fā)人員編寫的 COM 代碼。我為許多富于創(chuàng)造性的使用 COM 的工作方式感到驚訝,有一些使 COM 工作的巧妙代碼可能連 Microsoft 都沒有想到。同樣,看到一些錯誤一次又一次地重犯,使我免不了心灰意懶。這些錯誤很多都與線程和安全有關(guān),完全不成比例,而這也正是 COM 文檔資料中最缺少的兩個領(lǐng)域。如果不仔細(xì)計劃,它們也是最可能遇到的并可能會絆住您的兩個領(lǐng)域。
              本月的“超酷代碼”專欄與以前的大多數(shù)專欄有所不同。它并未提供一段可在您自己的應(yīng)用程序中使用的超酷代碼。相反,它將講述實現(xiàn)基于 COM 的應(yīng)用程序的正確方式和錯誤方式。它將講述一些來自艱難實踐的教訓(xùn),以及如何避免落入已經(jīng)讓許多 COM 開發(fā)人員吃盡苦頭的陷阱。
              在下面的篇幅中,您將讀到八位程序員的記述,這些教訓(xùn)都來自他們的痛苦經(jīng)歷。每個故事都是真實的,但為了保護(hù)無辜者,名字都已隱去。我的目的是,通過這些真實的 COM 故事,使您不再重蹈其他 COM 程序員的覆轍。它們還可能會幫助您在編寫的代碼中找出存在潛在問題的地方。無論情況如何,我想您都會獲得愉快的閱讀體驗。

          1. 總是調(diào)用 CoInitialize(Ex)
          2. 不要在線程之間傳遞原始接口指針
          3. STA 線程需要消息循環(huán)
          4. 單元模型對象必須保護(hù)共享數(shù)據(jù)
          5. 謹(jǐn)慎啟動用戶
          6. DCOM 不適于防火墻
          7. 使用線程或異步調(diào)用來避免 DCOM 超時設(shè)定太長
          8. 共享對象并不容易
          9. 與我聯(lián)系
          10. 總是調(diào)用 CoInitialize(Ex)
              幾個月前,我收到了一封朋友的電子郵件,他就職于一家著名的硬件公司。他的公司編寫了一個非常復(fù)雜的基于 COM 的應(yīng)用程序,其中使用了許多進(jìn)程內(nèi)和本地(進(jìn)程外)的 COM 組件。在開始時,應(yīng)用程序創(chuàng)建了 COM 對象以服務(wù)于運行在多線程單元 (MTA) 中的各種客戶端線程。該對象還可以托管給 MTA,這意味著接口指針可以在客戶端線程之間自由交換。在測試中,我的朋友發(fā)現(xiàn)在應(yīng)用程序準(zhǔn)備關(guān)閉之前,一切都進(jìn)行得不錯。然后,不知是什么原因,對 Release 的調(diào)用(必須執(zhí)行此調(diào)用,以便正確釋放客戶端占用的接口指針)被鎖定了。他的問題是:“到底是哪里出了問題?”
              其實答案非常簡單。應(yīng)用程序的開發(fā)人員其他都做得很對,只有一點例外,而這點又非常重要:他們沒有在所有的客戶端線程中調(diào)用 CoInitialize 或 CoInitializeEx。現(xiàn)代 COM 的基本原則之一,就是每個使用 COM 的線程都應(yīng)該先調(diào)用 CoInitialize 或 CoInitializeEx 來初始化 COM。這條原則是無法免除的。除了其他事情以外,CoInitialize(Ex) 應(yīng)將線程放入單元中,并初始化重要的每線程狀態(tài)信息(這對于 COM 的正確操作是必需的)。調(diào)用 CoInitialize(Ex) 失敗通常會在應(yīng)用程序生命期早期以失敗的 COM API 函數(shù)的形式表現(xiàn)出來,最常見的是激活請求。但有時問題很隱蔽,直到一切都太晚了(例如對 Release 的調(diào)用一去不復(fù)返了)才表現(xiàn)出來。當(dāng)開發(fā)小組將 CoInitialize(Ex) 調(diào)用添加到所有接觸 COM 的線程之后,他們的問題就迎刃而解了。
              具有諷刺意義的是,Microsoft 竟是 COM 程序員有時不調(diào)用 CoInitialize(Ex) 的原因之一。Microsoft 知識庫中包含的一些文檔中說,調(diào)用 CoInitialize(Ex) 對基于 MTA 的線程來說不是必需的(有關(guān)示例,請參閱文章 Q150777)。是的,在很多情況下,我們可以跳過 CoInitialize(Ex) 而不會出現(xiàn)問題。但是,這樣是不應(yīng)該的,除非您知道自己在干什么,并且可以絕對肯定自己不會受到負(fù)面影響。調(diào)用 CoInitialize(Ex) 是沒有害處的,因此我建議 COM 程序員始終從某個與 COM 相關(guān)的線程中調(diào)用它。

            不要在線程之間傳遞原始接口指針
              我咨詢的首批 COM 項目之一就涉及到一個包含 100,000 行代碼的分布式應(yīng)用程序,該程序是由美國西海岸的一個大型軟件公司編寫的。該應(yīng)用程序在多個機(jī)器上創(chuàng)建了數(shù)十個 COM 對象,并從客戶端進(jìn)程啟動的背景線程中調(diào)用這些對象。開發(fā)小組遇到問題了,調(diào)用要么消失得無影無蹤,要么在沒有明顯原因的情況下失敗。他們給我演示的最驚人的癥狀是:當(dāng)一個調(diào)用無法返回時,在同一臺機(jī)器上啟動其他支持 COM 的應(yīng)用程序(包括 Microsoft Paint 等)會頻繁導(dǎo)致這些應(yīng)用程序被鎖定。
              檢查他們的代碼后發(fā)現(xiàn),他們違反了 COM 并發(fā)的一個基本規(guī)則,就是說,如果一個線程要與另一個線程共享一個接口指針,它應(yīng)首先封送該接口指針。如果有必要,封送接口指針可使 COM 創(chuàng)建一個新的代理(以及一個新的信道對象,將代理和存根結(jié)對),以允許從另一個單元向外調(diào)用。不通過封送而將原始接口指針(內(nèi)存中的一個 32 位地址)傳遞給另一個線程,會繞過 COM 的并發(fā)機(jī)制,并且如果發(fā)送和接收的線程位于不同的單元中,將出現(xiàn)各種不良行為。(在 Windows 2000 中,由于兩個對象可以共享一個單元,但又位于不同的上下文中,因此如果線程位于同一個單元中,可能會使您陷入困境。)典型的癥狀包括調(diào)用失敗和返回 RPC_E_WRONG_THREAD_ERROR。
               Windows NT 4.0 和更高版本可以使用一對名為 CoMarshalInterThreadInterfaceInStream 和 CoGetInterfaceAndReleaseStream 的 API 函數(shù),在線程之間輕松地封送接口指針。假定您應(yīng)用程序中的一個線程(線程 A)創(chuàng)建了一個 COM 對象,繼而接收了一個 IFoo 接口指針,并且同一進(jìn)程中的另一個線程(線程 B)想調(diào)用這個對象。在準(zhǔn)備將接口指針傳遞給線程 B 時,線程 A 應(yīng)該封送該接口指針,如下所示:

            CoMarshalInterThreadInterfaceInStream (IID_IFoo, pFoo, &pStream);
            
            在 CoMarshalInterThreadInterfaceInStream 返回后,線程 B 就可以安全地取消封送該接口指針:
            IFoo* pFoo;
            CoGetInterfaceAndReleaseStream (pStream, IID_IFoo, (void**) &pFoo);
            
              在這些示例中,pFoo 是一個 IFoo 接口指針,pStream 是一個 IStream 接口指針。COM 在調(diào)用 CoMarshalInterThreadInterfaceInStream 時初始化 IStream 接口指針,然后在 CoGetInterfaceAndReleaseStream 內(nèi)部使用和釋放該接口指針。實際上,您通常要使用一個事件或其他同步化基元來協(xié)調(diào)這兩個線程的行為 — 例如,讓線程 B 知道接口指針已準(zhǔn)備好,可以取消封送。
              請注意,以這種方式封送接口指針不會出現(xiàn)任何問題,因為 COM 有足夠的智能,在不需要進(jìn)行封送時不會去封送(或重新封送)指針。如果在線程之間傳遞接口指針時這樣做,使用 COM 就輕松多了。
              如果調(diào)用 CoMarshalInterThreadInterfaceInStream 和 CoGetInterfaceAndReleaseStream 看起來太麻煩,您還可以通過將接口指針放在全局接口表 (GIT) 中,并讓其他線程去那里檢索它們,從而實現(xiàn)在線程之間傳遞接口指針。從 GIT 中檢索到的接口指針在被檢索時會自動封送。更多信息,請參閱 IGlobalInterfaceTable 中的文檔。請注意,GIT 只存在于 Windows NT 4.0 Service Pack 4 及更高版本中。

            STA 線程需要消息循環(huán)
              上一部分中描述的應(yīng)用程序還有另一個致命缺陷。看看您是否能指出來。
            這個特殊的應(yīng)用程序恰好是用 MFC 編寫的。在一開始,它使用了 MFC 的 AfxBeginThread 函數(shù)啟動一系列輔助線程。每個輔助線程要么調(diào)用 CoInitialize 要么調(diào)用 AfxOleInit(MFC 中類似 CoInitialize 的函數(shù))來初始化 COM。某些輔助線程則調(diào)用 CoCreateInstance 來創(chuàng)建 COM 對象,并將所返回的接口指針封送到其他輔助線程。從創(chuàng)建這些對象的線程中調(diào)用對象將非常順利,但從其他線程的調(diào)用卻從不返回。您知道這是為什么嗎?
              如果您認(rèn)為問題與消息循環(huán)(或缺少消息循環(huán))相關(guān),那么答案完全正確。事實確實如此。當(dāng)一個線程調(diào)用 CoInitialize 或 AfxOleInit 時,它是放在單線程單元 (STA) 中。當(dāng) COM 創(chuàng)建一個 STA 時,它會創(chuàng)建一個隨附的隱藏窗口。以 STA 中的對象為目標(biāo)的方法調(diào)用將轉(zhuǎn)換為消息,并放入與該 STA 關(guān)聯(lián)的窗口的消息隊列中。當(dāng)運行在該 STA 中的線程檢索到代表方法調(diào)用的消息時,隱藏窗口的窗口過程就會將消息轉(zhuǎn)換回方法調(diào)用。COM 使用 STA 執(zhí)行調(diào)用序列化。STA 中的對象一次不可能接收一個以上的調(diào)用,因為每個調(diào)用都要傳遞給一個而且是惟一一個運行在對象單元中的線程。
              如果基于 STA 的線程無法處理消息會怎么樣呢?如果它沒有消息循環(huán)又會怎么樣呢?針對該 STA 中對象的單元間方法調(diào)用將不再返回;它們將在消息隊列中被永遠(yuǎn)擱置。MFC 輔助線程中沒有消息循環(huán),因此如果寄宿在這些 STA 中的對象要從其他單元的客戶端接收方法調(diào)用,那么 MFC 輔助線程和 STA 是配合不好的。
              這個故事的寓意何在呢?STA 線程需要消息循環(huán),除非您肯定它們不會包含要從其他線程調(diào)用的對象。消息循環(huán)可以像這樣簡單:
            MSG msg;
            while (GetMessage (&msg, 0, 0, 0))
            DispatchMessage (&msg);
            
              另一種方案是將 COM 線程移到 MTA 中(或者在 Windows 2000 中,移到中立線程單元,即 NTA 中),這里沒有消息隊列依賴項。

            單元模型對象必須保護(hù)共享數(shù)據(jù)
              另一個困擾 COM 開發(fā)人員的通病是標(biāo)記為 ThreadingModel=Apartment 的進(jìn)程內(nèi)對象。這項指定告訴 COM,對象的實例必須只能在 STA 中創(chuàng)建。它還可讓 COM 自由地將這些對象實例放在任何主機(jī)進(jìn)程的 STA 中。
              假設(shè)客戶端應(yīng)用程序有五個 STA 線程,每個線程都使用 CoCreateInstance 來創(chuàng)建同一個對象的一個實例。如果線程是基于 STA 的,且對象標(biāo)記為 ThreadingModel=Apartment,則這五個對象實例將在對象創(chuàng)建者的 STA 中創(chuàng)建。因為每個對象實例都在占用其 STA 的線程上運行,因此所有五個對象實例都可以并行運行。
              到目前為止,一切良好。現(xiàn)在考慮一下,如果這些對象實例共享數(shù)據(jù)會發(fā)生什么情況。因為對象都在并發(fā)線程上執(zhí)行,兩個或更多的對象可能會同時嘗試訪問同一個數(shù)據(jù)。除非所有這些訪問都是讀取訪問,否則就會釀成災(zāi)難。問題可能不會很快顯現(xiàn)出來;它們會以和時間緊密相關(guān)的錯誤形式出現(xiàn),因此很難診斷和重現(xiàn)。這就解釋了以下事實的原因:ThreadingModel=Apartment 對象應(yīng)該包括可同步對共享數(shù)據(jù)的訪問的代碼,除非您能夠確定對象的客戶端不會對執(zhí)行訪問的方法進(jìn)行重疊調(diào)用。
              問題在于,太多的 COM 開發(fā)人員相信 ThreadingModel=Apartment 能夠使他們免于編寫線程安全的代碼。事實并非如此 — 至少不完全如此。ThreadingModel=Apartment 并不意味著對象必須是完全線程安全的,它代表的是一個對 COM 的承諾,即訪問兩個或更多對象實例共享的數(shù)據(jù)(或此對象和其他對象的實例共享的數(shù)據(jù))時是以線程安全的方式進(jìn)行的。而提供該線程安全性的任務(wù)應(yīng)該由您,即對象實現(xiàn)者來負(fù)責(zé)。共享數(shù)據(jù)的類型和大小多種多樣,但大多是以全局變量、C++ 類中的靜態(tài)成員變量和函數(shù)中聲明的靜態(tài)變量的形式出現(xiàn)。即使是以下這樣無害的語句也會在 STA 中出問題:
            static int nCallCount = 0;
            nCallCount++;
            
            因為這個對象的所有實例都將共享一個 nCallCount 實例,編寫這些語句的正確方式如下:
            static int nCallCount = 0;
            InterlockIncrement (&nCallCount);
            
            注意:您可以使用臨界區(qū)、互鎖函數(shù)或您希望的任何方式,但不要忘了訪問基于 STA 的對象共享的數(shù)據(jù)時要進(jìn)行同步化!

            謹(jǐn)慎啟動用戶
              這里還有一個問題讓許多 COM 開發(fā)人員都吃過苦頭。去年春天,有一家公司向我緊急呼救,他們的開發(fā)人員使用 COM 構(gòu)建了一個分布式應(yīng)用程序,其中客戶端進(jìn)程運行在與遠(yuǎn)程服務(wù)器的 Singleton 對象相連接的網(wǎng)絡(luò)工作站上。在測試過程中,他們遇到了一些非常奇怪的行為。在一種測試場景中,客戶端對 CoCreateInstanceEx 的調(diào)用可使它們與 Singleton 對象正常連接。而在另一個場景中,對 CoCreateInstanceEx 的相同調(diào)用產(chǎn)生了多個對象實例和多個服務(wù)器進(jìn)程,使客戶端無法與同一個對象   實例連接,從而實際影響了應(yīng)用程序。在這兩個場景中,硬件和軟件是完全相同的。
              此問題似乎與安全有關(guān)。當(dāng)處理遠(yuǎn)程激活請求的 COM 服務(wù)控制管理器 (SCM) 在另一臺機(jī)器上啟動一個進(jìn)程時,它會為該進(jìn)程分配一個標(biāo)識。除非另外指定,它選擇的標(biāo)識就是啟動用戶的標(biāo)識。換句話說,分配給服務(wù)器進(jìn)程的標(biāo)識與啟動它的客戶端進(jìn)程的標(biāo)識相同。在這種情況下,如果 Bob 登錄機(jī)器 A,并使用 CoCreateInstanceEx 連接機(jī)器 B 上的 Singleton 對象,而 Alice 也在機(jī)器 C 上如法炮制,就會啟動兩個不同的服務(wù)器進(jìn)程(至少在兩臺不同的 WinStation 上),實際上使客戶端無法再用 Singleton 語義與共享的對象實例連接。
              兩個測試場景之所以會產(chǎn)生大相徑庭的結(jié)果,其原因就是在一個場景(那個可以工作的場景)中,所有測試人員都使用只為測試而設(shè)置的一個特殊帳戶以同一個人的身份登錄。而在另一個場景中,測試人員都使用他們的普通用戶帳戶登錄。當(dāng)兩個或更多的客戶端進(jìn)程具有相同標(biāo)識時,它們可以成功連接到配置為假定啟動用戶標(biāo)識的服務(wù)器進(jìn)程。但是,如果客戶端有不同的標(biāo)識,SCM 會使用多個服務(wù)器進(jìn)程(每個唯一客戶端標(biāo)識一個)分隔分配給不同對象實例的標(biāo)識。


            圖 1 DCOMCNFG 中的用戶帳戶

              找到問題以后,解決起來就很簡單了:配置 COM 服務(wù)器,讓其使用特定的用戶帳戶而不是假定啟動用戶的標(biāo)識。完成這一任務(wù)的一種方式是在服務(wù)器機(jī)器上運行 DCOMCNFG(Microsoft 的 DCOM 配置工具),并將“l(fā)aunching user ”更改為“This user”(請參見圖 1)。如果您喜歡通過編程方式進(jìn)行更改(可能從安裝程序著手),請在主機(jī)注冊表的 HKEY_CLASSES_ROOT\AppID 部分的 COM 服務(wù)器項中添加 RunAs 值(請參見圖 2)。


            圖 2 添加 RunAs 值到注冊表中

              您還需要使用 LsaStorePrivateData 將 RunAs 帳戶的密碼存儲為 LSA 密鑰,并使用 LsaAddAccountRights 確保帳戶擁有“Logon as batch job”的權(quán)限。(有關(guān)具體操作的示例,請參見 Platform SDK 中的 DCOMPERM 示例。請?zhí)貏e注意名為 SetRunAsPassword 和 SetAccountRights 的函數(shù)。)

            ? DCOM 不適于防火墻
              關(guān)于 DCOM 特性和功能的一個常見問題是:“它能跨 Internet 工作嗎?”DCOM 能夠很好地跨 Internet 工作,只要將它配置為使用 TCP 或者 UDP,并且通過授予任何人啟動和訪問權(quán)限,可將服務(wù)器配置為允許匿名方法調(diào)用。畢竟,Internet 是一個巨大的 IP 網(wǎng)絡(luò)。但矛盾的是,如果您將一個現(xiàn)有的 DCOM 應(yīng)用程序(在公司的內(nèi)部網(wǎng)絡(luò)或 intranet 中工作得很好)改為跨 Internet 工作,它很有可能失敗得很慘。可能是什么原因呢?防火墻。
              DCOM 生來與防火墻的關(guān)系就如油與水的關(guān)系。原因之一是 COM 的 SCM 使用端口 135 與其他機(jī)器上的 SCM 通信。防火墻限制了它可以使用的端口和協(xié)議,可能會拒絕通過端口 135 傳入的通信量。但更大的問題在于,為了避免與使用套接字、管道和其他 IPC 機(jī)制的應(yīng)用程序沖突,DCOM 沒有固定使用特定范圍的端口,相反,它在運行時才選擇所使用的端口。默認(rèn)情況下,它可以使用從 1,024 到 65,535 范圍內(nèi)的任何端口。
              允許 DCOM 應(yīng)用程序通過防火墻的一種方式是,為 DCOM 要使用的協(xié)議打開端口 135 和端口 1,024-65,535。(默認(rèn)情況下,Windows NT 4.0 是 UDP 協(xié)議,Windows 2000 是 TCP 協(xié)議。)但是,這比移除所有防火墻好不了多少。對此,您公司的 IT 人員可能要發(fā)表意見了。
            另一種更安全和更現(xiàn)實的解決方案是,限制 DCOM 使用的端口范圍,并只為 DCOM 通信量打開一組小范圍端口。根據(jù)實踐原則,您應(yīng)該為每個服務(wù)器進(jìn)程分配一個端口,將連接導(dǎo)出到遠(yuǎn)程 COM 客戶端(不是每個接口指針一個端口或每個對象一個端口,而是每個服務(wù)器進(jìn)程一個)。將 DCOM 配置為使用 TCP 而不是 UDP 是一個好方法,特別是在服務(wù)器對其客戶端執(zhí)行回調(diào)時。
              DCOM 用于遠(yuǎn)程連接的端口范圍和所用的協(xié)議可通過注冊表進(jìn)行配置。在 Windows 2000 和 Windows NT 4.0 Service Pack 4 或更高版本上,您可以用 DCOMCNFG 應(yīng)用這些配置更改。以下是將 DCOM 配置為通過防火墻工作的辦法。


            圖 3 選擇協(xié)議
            • 在服務(wù)器(在防火墻后寄存遠(yuǎn)程對象的機(jī)器)上,將 DCOM 配置為使用 TCP 作為其所選協(xié)議,如圖 3 中所示。
            • 在服務(wù)器上,限制 DCOM 將使用的端口范圍。記住為每個服務(wù)器進(jìn)程至少分配一個端口。圖 4 中的示例將 DCOM 限制為端口 8,192 到 8,195。
            • 打開您在步驟 2 中選擇的端口,使 TCP 通信量能夠通過防火墻。同時打開端口 135。


            圖 4 選擇端口

              執(zhí)行這些步驟,DCOM 就可以很好地跨防火墻工作了。如果您愿意,SP4 和更高版本還可讓您為單獨的 COM 服務(wù)器指定終結(jié)點。更多信息,請閱讀 Michael Nelson 關(guān)于 DCOM 和防火墻的優(yōu)秀論文,該論文可在 MSDN Online 站點上找到(請參見 http://msdn.microsoft.com/library/en-us/dndcom/html/msdn_dcomfirewall.asp)。
              還應(yīng)注意的是,通過在服務(wù)器上安裝 Internet 信息服務(wù) (IIS),并使用 COM Internet 服務(wù) (CIS) 通過端口 80 路由 DCOM 通信量,SP4 和更高版本的用戶還可以使用 CIS 來提供與防火墻兼容的 DCOM。有關(guān)該主題的更多信息,請參閱 http://msdn.microsoft.com/library/en-us/dndcom/html/cis.asp

            使用線程或異步調(diào)用來避免 DCOM 超時設(shè)定太長
              總是有人問我當(dāng) DCOM 無法完成遠(yuǎn)程實例化請求或方法調(diào)用時出現(xiàn)的超時設(shè)定太長的問題。典型的場景如下:客戶端調(diào)用 CoCreateInstanceEx 來實例化遠(yuǎn)程機(jī)器上的一個對象,但是這臺機(jī)器臨時離線了。在 Windows NT 4.0 上,激活請求不會立即失敗,DCOM 可能會花上一分鐘或更長時間來返回失敗的 HRESULT。DCOM 還可能花費很長時間,使指向已不再存在或其主機(jī)已離線的遠(yuǎn)程對象的方法調(diào)用失敗。如果可能,開發(fā)人員應(yīng)該如何避免這些較長的超時設(shè)定呢?
              要回答這個問題,幾句話是講不清楚的。DCOM 高度依賴于基礎(chǔ)網(wǎng)絡(luò)協(xié)議和 RPC 子系統(tǒng)。并沒有什么神奇的設(shè)置可讓您限制 DCOM 超時設(shè)定的持續(xù)時間。但是,我經(jīng)常使用兩種技巧來避免較長超時設(shè)定的負(fù)作用。
              在 Windows 2000 中,當(dāng)調(diào)用在 COM 信道中掛起時,您可以使用異步方法調(diào)用來釋放調(diào)用線程。(有關(guān)異步方法調(diào)用的介紹,請參 MSDN Magazine 2000 年 4 月刊的“Windows 2000: Asynchronous Method Calls Eliminate the Wait for COM Clients and Servers Alike”。如果異步調(diào)用在合理時間內(nèi)沒有返回,您可以通過調(diào)用用于初始化調(diào)用的調(diào)用對象上的 ICancelMethodCalls::Cancel 來取消它。
               Windows NT 4.0 不支持異步方法調(diào)用,甚至在 Windows 2000 中也不支持異步激活請求。怎么解決呢?從背景線程調(diào)用遠(yuǎn)程對象(或是實例化該對象的請求)。使主線程在事件對象上阻塞,并指定超時設(shè)定值以反映您愿意等待的時間長度。當(dāng)調(diào)用返回時,讓背景線程來設(shè)置事件。假設(shè)主線程使用 WaitForSingleObject 阻塞,當(dāng) WaitForSingleObject 返回時,返回值可以告訴您,返回是因為方法調(diào)用或激活請求返回,還是因為您在 WaitForSingleObject 調(diào)用中指定的超時設(shè)定到期。您不能在 Windows NT 4.0 中取消掛起調(diào)用,但是至少主線程可以自由地執(zhí)行自己的任務(wù)。
              下面的代碼演示了基于 Windows NT 4.0 的客戶端如何才能從背景線程調(diào)用對象。

            //////////////////////////////////////////////////////
            // Placing a Method Call from a Background Thread 
            ///////////////////////////////////////////////////// 
            HANDLE g_hEvent;
            IStream* g_pStream;
            
            // Thread A
            g_hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
            CoMarshalInterThreadInterfaceInStream (IID_IFoo, pFoo, &g_pStream);
            DWORD dwThreadID;
            CreateThread (NULL, 0, ThreadFunc, NULL, 0, &dwThreadID);
            DWORD dw = WaitForSingleObject (g_hEvent, 5000);
            if (dw == WAIT_TIMEOUT) {
                // Call timed out
            }
            else {
                // Call completed
            }
            ...
            
            // Thread B
            IFoo* pFoo;
            CoGetInterfaceAndReleaseStream (g_pStream, IID_IFoo, (void**) &pFoo);
            pFoo->Bar (); // Make the call!
            SetEvent (g_hEvent);
            CloseHandle (g_hEvent);      
              在此示例中,線程 A 封送了一個 IFoo 接口指針,并啟動線程 B。線程 B 取消封送了該接口指針,并調(diào)用 IFoo::Bar。無論調(diào)用返回所花費的時間有多長,線程 A 都不會阻塞超過 5 秒鐘,因為它在 WaitForSingleObject 的第二個參數(shù)中傳遞的是 5,000 (單位為微秒)。這并不是太好的辦法,但是如果“無論在線路的另一端發(fā)生什么情況,線程 A 都不會掛起”這一點很重要的話,忍受這種麻煩也算值得。

            共享對象并不容易
              從我收到的郵件和在會議上被問到的問題判斷,困擾許多 COM 程序員的一個問題是如何將兩個或更多的客戶端與一個對象實例連接。要回答這個問題,寫出長篇大論(或是一本小冊子)都很容易,但其實只要說明與現(xiàn)有對象的連接既不容易也不自動化,就足夠了。COM 提供了大量創(chuàng)建對象的方式,包括很受歡迎的 CoCreateInstance(Ex) 函數(shù)。但是 COM 缺乏一種通用的命名服務(wù),允許使用名稱或 GUID 來標(biāo)識對象實例。而且它沒有提供內(nèi)置的方式來創(chuàng)建對象,然后將它標(biāo)識為調(diào)用的目標(biāo)以檢索接口指針。
              這是不是意味著將多個客戶端與單一對象實例連接就不可能了呢?當(dāng)然不是。實現(xiàn)這一點有五種方式。在這些資源鏈接中,您可以找到更多信息甚至是示例代碼,來指導(dǎo)您的操作。請注意,這些技術(shù)從一般意義上講不能互換;通常,環(huán)境因素會決定哪種方式(如果有)適用于手邊的任務(wù): Singleton 對象 Singleton 對象就是只實例化一次的對象。可能會有 10 個客戶端調(diào)用 CoCreateInstance 來“創(chuàng)建”Singleton 對象,但實際上,它們都是接收指向同一對象的接口指針。ATL COM 類可通過在其類的    聲明中添加 DECLARE_CLASSFACTORY_SINGLETON 語句,來轉(zhuǎn)換為 Singleton。
              文件名字對象 如果一個對象實現(xiàn)了 IpersistFile,并在運行中對象表 (ROT) 中使用文件名字對象(它封裝了傳遞給對象的 IPersistFile::Load 方法的文件名稱)注冊了自己,那么客戶端就可以使用文件名字對象連接對象的現(xiàn)有實例了。實際上,文件名字對象允許使用文件名稱來命名對象實例,對象可在這些文件名稱中存儲它們的持久性數(shù)據(jù)。它們甚至能夠跨機(jī)器工作。
              CoMarshalInterface 和 CoUnmarshalInterface 保存接口指針的 COM 客戶端可以與其他客戶端共享這些接口指針,只要它們愿意封送指針。COM 為愿意將接口指針封送給同一進(jìn)程中其他線程的線程提供了優(yōu)化(請參見教訓(xùn) 2),但是如果客戶端線程屬于其他進(jìn)程,CoMarshalInterface 和 CoUnmarshalInterface 就是實現(xiàn)接口共享的關(guān)鍵途徑了。有關(guān)討論和示例代碼,請參閱 MSJ 1999 年 8 月刊中我的超酷代碼專欄。
              自定義類對象 所有“可以在外部創(chuàng)建的”COM 對象都伴隨有獨立對象,稱為類對象,它們的作用是創(chuàng)建所謂 COM 對象的實例。大多數(shù)類對象都要實現(xiàn)一個名為 IClassFactory 的接口,其中包括一個可以創(chuàng)建對象實例的名為 CreateInstance 的方法。(在底層,COM 將 CoCreateInstance(Ex) 調(diào)用轉(zhuǎn)換為對 IClassFactory::CreateInstance 的調(diào)用)。IClassFactory 的問題在于,在檢索指向以前創(chuàng)建的對象實例的接口指針時,它一點用處都沒有。自定義類對象是實現(xiàn)替代 IClassFactory 的自定義激活接口的類對象。由于定義了接口,您還可以定義方法來檢索對已由該類對象創(chuàng)建的對象的引用。更多信息和以 ATL 編寫的示例,請參閱 1999 年 2 月的超酷代碼專欄。
              自定義名字對象 1999 年 11 月的超酷代碼專欄介紹了允許使用 C 樣式字符串命名對象實例的自定義名字對象類。將一個實例名稱傳遞給 MkParseDisplayName,您就獲得了一個與對象實例連接的名字對象。這些類型的名字對象的一個缺點是,它們在 Windows NT 4.0 中不能跨機(jī)器工作。
            在使用這些方法中的任一個在客戶端之間共享對象實例之前,請問自己一個問題:共享是必需的嗎?如果方法調(diào)用從一個外部數(shù)據(jù)源(數(shù)據(jù)庫、硬件設(shè)備,甚至可能是全局變量)檢索數(shù)據(jù)以響應(yīng)客戶端的請求,那么為什么不為每個客戶端分配一個對象實例,并允許每個實例訪問數(shù)據(jù)源呢?您可能必須同步化對數(shù)據(jù)源的訪問,但不一定要使用自定義類對象、自定義名字對象等手段。這就是用 Microsoft 事務(wù)服務(wù) (MTS) 構(gòu)建的應(yīng)用程序的工作方式,從各種理由來看,它已經(jīng)證明是一種引人注目的編程模型,而不僅僅是實現(xiàn)上的簡便和性能的改善。

            與我聯(lián)系
              您有什么困難的 Win32、MFC、MTS 或 COM(+) 編程問題需要解答嗎?如果有,請給我發(fā)郵件,地址是 jeffpro@msn.com。請在郵件主題中加上“Wicked Code”(超酷代碼)字樣。請原諒時間不允許我個別回答這些問題,但是對于每個問題,我都會考慮加入到未來的專欄內(nèi)容中。
              Jeff Prosise 是 Programming Windows with MFC(Microsoft Press,1999 年)一書的作者。他還是 Wintellect(一家軟件咨詢和教育公司)的創(chuàng)辦人之一,該公司的網(wǎng)址是 http://www.wintellect.com

            摘自 MSDN Magazine2000 年 11 月刊。此雜志可通過各地的報攤購買,也可以訂閱

            本文由 VCKBASE MTT 翻譯
            posted on 2006-09-19 21:10 鐵觀音 閱讀(211) 評論(0)  編輯 收藏 引用 所屬分類: COM技術(shù)
            久久综合精品国产一区二区三区| 国产精品美女久久久久| 91久久福利国产成人精品| 亚洲国产二区三区久久| 三级片免费观看久久| 亚洲AV无码久久精品狠狠爱浪潮| www久久久天天com| 欧美精品一区二区久久| 国产成人精品白浆久久69| 日本精品久久久久影院日本| 狠狠88综合久久久久综合网 | 漂亮人妻被中出中文字幕久久| 五月丁香综合激情六月久久| 国产精品va久久久久久久| 亚洲中文久久精品无码ww16| 久久久久国色AV免费观看| 99久久无码一区人妻a黑| 精品无码久久久久国产动漫3d| 国产精品成人99久久久久 | 久久亚洲国产精品123区| 久久99国产综合精品女同| 久久中文字幕无码专区| 狠狠色丁香久久综合五月| 麻豆成人久久精品二区三区免费| 久久久久国产成人精品亚洲午夜| 精品久久久噜噜噜久久久| 精品国产乱码久久久久久呢 | 国产巨作麻豆欧美亚洲综合久久 | 久久中文字幕精品| 办公室久久精品| 成人a毛片久久免费播放| 97超级碰碰碰碰久久久久| 久久免费高清视频| 久久青草国产手机看片福利盒子| 久久天天躁狠狠躁夜夜躁2O2O| 97精品国产97久久久久久免费 | 品成人欧美大片久久国产欧美| 国产精品久久久久久影院| 青青青青久久精品国产| 超级碰久久免费公开视频| 精品久久久久一区二区三区|