• <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>

            牽著老婆滿街逛

            嚴(yán)以律己,寬以待人. 三思而后行.
            GMail/GTalk: yanglinbo#google.com;
            MSN/Email: tx7do#yahoo.com.cn;
            QQ: 3 0 3 3 9 6 9 2 0 .

            超酷代碼:來(lái)自 COM 經(jīng)驗(yàn)的八個(gè)教訓(xùn)

            布日期: 5/20/2004 | 更新日期: 5/20/2004

            Jeff Prosise

            在日常工作中,我看到過(guò)許多由不同開(kāi)發(fā)人員編寫的 COM 代碼。我為許多富于創(chuàng)造性的使用 COM 的工作方式感到驚訝,有一些使 COM 工作的巧妙代碼可能連 Microsoft 都沒(méi)有想到。同樣,看到一些錯(cuò)誤一次又一次地重犯,使我免不了心灰意懶。這些錯(cuò)誤很多都與線程和安全有關(guān),完全不成比例,而這也正是 COM 文檔資料中最缺少的兩個(gè)領(lǐng)域。如果不仔細(xì)計(jì)劃,它們也是最可能遇到的并可能會(huì)絆住您的兩個(gè)領(lǐng)域。

            本月的“超酷代碼”專欄與以前的大多數(shù)專欄有所不同。它并未提供一段可在您自己的應(yīng)用程序中使用的超酷代碼。相反,它將講述實(shí)現(xiàn)基于 COM 的應(yīng)用程序的正確方式和錯(cuò)誤方式。它將講述一些來(lái)自艱難實(shí)踐的教訓(xùn),以及如何避免落入已經(jīng)讓許多 COM 開(kāi)發(fā)人員吃盡苦頭的陷阱。

            在下面的篇幅中,您將讀到八位程序員的記述,這些教訓(xùn)都來(lái)自他們的痛苦經(jīng)歷。每個(gè)故事都是真實(shí)的,但為了保護(hù)無(wú)辜者,名字都已隱去。我的目的是,通過(guò)這些真實(shí)的 COM 故事,使您不再重蹈其他 COM 程序員的覆轍。它們還可能會(huì)幫助您在編寫的代碼中找出存在潛在問(wèn)題的地方。無(wú)論情況如何,我想您都會(huì)獲得愉快的閱讀體驗(yàn)。

            *
            本頁(yè)內(nèi)容
            總是調(diào)用 CoInitialize(Ex)總是調(diào)用 CoInitialize(Ex)
            不要在線程之間傳遞原始接口指針不要在線程之間傳遞原始接口指針
            STA 線程需要消息循環(huán)STA 線程需要消息循環(huán)
            單元模型對(duì)象必須保護(hù)共享數(shù)據(jù)單元模型對(duì)象必須保護(hù)共享數(shù)據(jù)
            謹(jǐn)慎啟動(dòng)用戶謹(jǐn)慎啟動(dòng)用戶
            DCOM 不適于防火墻DCOM 不適于防火墻
            使用線程或異步調(diào)用來(lái)避免 DCOM 超時(shí)設(shè)定太長(zhǎng)使用線程或異步調(diào)用來(lái)避免 DCOM 超時(shí)設(shè)定太長(zhǎng)
            共享對(duì)象并不容易共享對(duì)象并不容易
            與我聯(lián)系與我聯(lián)系

            總是調(diào)用 CoInitialize(Ex)

            幾個(gè)月前,我收到了一封朋友的電子郵件,他就職于一家著名的硬件公司。他的公司編寫了一個(gè)非常復(fù)雜的基于 COM 的應(yīng)用程序,其中使用了許多進(jìn)程內(nèi)和本地(進(jìn)程外)的 COM 組件。在開(kāi)始時(shí),應(yīng)用程序創(chuàng)建了 COM 對(duì)象以服務(wù)于運(yùn)行在多線程單元 (MTA) 中的各種客戶端線程。該對(duì)象還可以托管給 MTA,這意味著接口指針可以在客戶端線程之間自由交換。在測(cè)試中,我的朋友發(fā)現(xiàn)在應(yīng)用程序準(zhǔn)備關(guān)閉之前,一切都進(jìn)行得不錯(cuò)。然后,不知是什么原因,對(duì) Release 的調(diào)用(必須執(zhí)行此調(diào)用,以便正確釋放客戶端占用的接口指針)被鎖定了。他的問(wèn)題是:“到底是哪里出了問(wèn)題?”

            其實(shí)答案非常簡(jiǎn)單。應(yīng)用程序的開(kāi)發(fā)人員其他都做得很對(duì),只有一點(diǎn)例外,而這點(diǎn)又非常重要:他們沒(méi)有在所有的客戶端線程中調(diào)用 CoInitialize 或 CoInitializeEx。現(xiàn)代 COM 的基本原則之一,就是每個(gè)使用 COM 的線程都應(yīng)該先調(diào)用 CoInitialize 或 CoInitializeEx 來(lái)初始化 COM。這條原則是無(wú)法免除的。除了其他事情以外,CoInitialize(Ex) 應(yīng)將線程放入單元中,并初始化重要的每線程狀態(tài)信息(這對(duì)于 COM 的正確操作是必需的)。調(diào)用 CoInitialize(Ex) 失敗通常會(huì)在應(yīng)用程序生命期早期以失敗的 COM API 函數(shù)的形式表現(xiàn)出來(lái),最常見(jiàn)的是激活請(qǐng)求。但有時(shí)問(wèn)題很隱蔽,直到一切都太晚了(例如對(duì) Release 的調(diào)用一去不復(fù)返了)才表現(xiàn)出來(lái)。當(dāng)開(kāi)發(fā)小組將 CoInitialize(Ex) 調(diào)用添加到所有接觸 COM 的線程之后,他們的問(wèn)題就迎刃而解了。

            具有諷刺意義的是,Microsoft 竟是 COM 程序員有時(shí)不調(diào)用 CoInitialize(Ex) 的原因之一。Microsoft 知識(shí)庫(kù)中包含的一些文檔中說(shuō),調(diào)用 CoInitialize(Ex) 對(duì)基于 MTA 的線程來(lái)說(shuō)不是必需的(有關(guān)示例,請(qǐng)參閱文章 Q150777)。是的,在很多情況下,我們可以跳過(guò) CoInitialize(Ex) 而不會(huì)出現(xiàn)問(wèn)題。但是,這樣是不應(yīng)該的,除非您知道自己在干什么,并且可以絕對(duì)肯定自己不會(huì)受到負(fù)面影響。調(diào)用 CoInitialize(Ex) 是沒(méi)有害處的,因此我建議 COM 程序員始終從某個(gè)與 COM 相關(guān)的線程中調(diào)用它。

            返回頁(yè)首返回頁(yè)首

            不要在線程之間傳遞原始接口指針

            我咨詢的首批 COM 項(xiàng)目之一就涉及到一個(gè)包含 100,000 行代碼的分布式應(yīng)用程序,該程序是由美國(guó)西海岸的一個(gè)大型軟件公司編寫的。該應(yīng)用程序在多個(gè)機(jī)器上創(chuàng)建了數(shù)十個(gè) COM 對(duì)象,并從客戶端進(jìn)程啟動(dòng)的背景線程中調(diào)用這些對(duì)象。開(kāi)發(fā)小組遇到問(wèn)題了,調(diào)用要么消失得無(wú)影無(wú)蹤,要么在沒(méi)有明顯原因的情況下失敗。他們給我演示的最驚人的癥狀是:當(dāng)一個(gè)調(diào)用無(wú)法返回時(shí),在同一臺(tái)機(jī)器上啟動(dòng)其他支持 COM 的應(yīng)用程序(包括 Microsoft Paint 等)會(huì)頻繁導(dǎo)致這些應(yīng)用程序被鎖定。

            檢查他們的代碼后發(fā)現(xiàn),他們違反了 COM 并發(fā)的一個(gè)基本規(guī)則,就是說(shuō),如果一個(gè)線程要與另一個(gè)線程共享一個(gè)接口指針,它應(yīng)首先封送該接口指針。如果有必要,封送接口指針可使 COM 創(chuàng)建一個(gè)新的代理(以及一個(gè)新的信道對(duì)象,將代理和存根結(jié)對(duì)),以允許從另一個(gè)單元向外調(diào)用。不通過(guò)封送而將原始接口指針(內(nèi)存中的一個(gè) 32 位地址)傳遞給另一個(gè)線程,會(huì)繞過(guò) COM 的并發(fā)機(jī)制,并且如果發(fā)送和接收的線程位于不同的單元中,將出現(xiàn)各種不良行為。(在 Windows 2000 中,由于兩個(gè)對(duì)象可以共享一個(gè)單元,但又位于不同的上下文中,因此如果線程位于同一個(gè)單元中,可能會(huì)使您陷入困境。)典型的癥狀包括調(diào)用失敗和返回 RPC_E_WRONG_THREAD_ERROR。

            Windows NT 4.0 和更高版本可以使用一對(duì)名為 CoMarshalInterThreadInterfaceInStream 和 CoGetInterfaceAndReleaseStream 的 API 函數(shù),在線程之間輕松地封送接口指針。假定您應(yīng)用程序中的一個(gè)線程(線程 A)創(chuàng)建了一個(gè) COM 對(duì)象,繼而接收了一個(gè) IFoo 接口指針,并且同一進(jìn)程中的另一個(gè)線程(線程 B)想調(diào)用這個(gè)對(duì)象。在準(zhǔn)備將接口指針傳遞給線程 B 時(shí),線程 A 應(yīng)該封送該接口指針,如下所示:

            CoMarshalInterThreadInterfaceInStream (IID_IFoo, pFoo, &pStream);
            

            在 CoMarshalInterThreadInterfaceInStream 返回后,線程 B 就可以安全地取消封送該接口指針:

            IFoo* pFoo;
            CoGetInterfaceAndReleaseStream (pStream, IID_IFoo, (void**) &pFoo);
            

            在這些示例中,pFoo 是一個(gè) IFoo 接口指針,pStream 是一個(gè) IStream 接口指針。COM 在調(diào)用 CoMarshalInterThreadInterfaceInStream 時(shí)初始化 IStream 接口指針,然后在 CoGetInterfaceAndReleaseStream 內(nèi)部使用和釋放該接口指針。實(shí)際上,您通常要使用一個(gè)事件或其他同步化基元來(lái)協(xié)調(diào)這兩個(gè)線程的行為 — 例如,讓線程 B 知道接口指針已準(zhǔn)備好,可以取消封送。

            請(qǐng)注意,以這種方式封送接口指針不會(huì)出現(xiàn)任何問(wèn)題,因?yàn)?COM 有足夠的智能,在不需要進(jìn)行封送時(shí)不會(huì)去封送(或重新封送)指針。如果在線程之間傳遞接口指針時(shí)這樣做,使用 COM 就輕松多了。

            如果調(diào)用 CoMarshalInterThreadInterfaceInStream 和 CoGetInterfaceAndReleaseStream 看起來(lái)太麻煩,您還可以通過(guò)將接口指針?lè)旁谌纸涌诒?(GIT) 中,并讓其他線程去那里檢索它們,從而實(shí)現(xiàn)在線程之間傳遞接口指針。從 GIT 中檢索到的接口指針在被檢索時(shí)會(huì)自動(dòng)封送。更多信息,請(qǐng)參閱 IGlobalInterfaceTable 中的文檔。請(qǐng)注意,GIT 只存在于 Windows NT 4.0 Service Pack 4 及更高版本中。

            返回頁(yè)首返回頁(yè)首

            STA 線程需要消息循環(huán)

            上一部分中描述的應(yīng)用程序還有另一個(gè)致命缺陷。看看您是否能指出來(lái)。

            這個(gè)特殊的應(yīng)用程序恰好是用 MFC 編寫的。在一開(kāi)始,它使用了 MFC 的 AfxBeginThread 函數(shù)啟動(dòng)一系列輔助線程。每個(gè)輔助線程要么調(diào)用 CoInitialize 要么調(diào)用 AfxOleInit(MFC 中類似 CoInitialize 的函數(shù))來(lái)初始化 COM。某些輔助線程則調(diào)用 CoCreateInstance 來(lái)創(chuàng)建 COM 對(duì)象,并將所返回的接口指針?lè)馑偷狡渌o助線程。從創(chuàng)建這些對(duì)象的線程中調(diào)用對(duì)象將非常順利,但從其他線程的調(diào)用卻從不返回。您知道這是為什么嗎?

            如果您認(rèn)為問(wèn)題與消息循環(huán)(或缺少消息循環(huán))相關(guān),那么答案完全正確。事實(shí)確實(shí)如此。當(dāng)一個(gè)線程調(diào)用 CoInitialize 或 AfxOleInit 時(shí),它是放在單線程單元 (STA) 中。當(dāng) COM 創(chuàng)建一個(gè) STA 時(shí),它會(huì)創(chuàng)建一個(gè)隨附的隱藏窗口。以 STA 中的對(duì)象為目標(biāo)的方法調(diào)用將轉(zhuǎn)換為消息,并放入與該 STA 關(guān)聯(lián)的窗口的消息隊(duì)列中。當(dāng)運(yùn)行在該 STA 中的線程檢索到代表方法調(diào)用的消息時(shí),隱藏窗口的窗口過(guò)程就會(huì)將消息轉(zhuǎn)換回方法調(diào)用。COM 使用 STA 執(zhí)行調(diào)用序列化。STA 中的對(duì)象一次不可能接收一個(gè)以上的調(diào)用,因?yàn)槊總€(gè)調(diào)用都要傳遞給一個(gè)而且是惟一一個(gè)運(yùn)行在對(duì)象單元中的線程。

            如果基于 STA 的線程無(wú)法處理消息會(huì)怎么樣呢?如果它沒(méi)有消息循環(huán)又會(huì)怎么樣呢?針對(duì)該 STA 中對(duì)象的單元間方法調(diào)用將不再返回;它們將在消息隊(duì)列中被永遠(yuǎn)擱置。MFC 輔助線程中沒(méi)有消息循環(huán),因此如果寄宿在這些 STA 中的對(duì)象要從其他單元的客戶端接收方法調(diào)用,那么 MFC 輔助線程和 STA 是配合不好的。

            這個(gè)故事的寓意何在呢?STA 線程需要消息循環(huán),除非您肯定它們不會(huì)包含要從其他線程調(diào)用的對(duì)象。消息循環(huán)可以像這樣簡(jiǎn)單:

            MSG msg;
            while (GetMessage (&msg, 0, 0, 0))
            DispatchMessage (&msg);
            

            另一種方案是將 COM 線程移到 MTA 中(或者在 Windows 2000 中,移到中立線程單元,即 NTA 中),這里沒(méi)有消息隊(duì)列依賴項(xiàng)。

            返回頁(yè)首返回頁(yè)首

            單元模型對(duì)象必須保護(hù)共享數(shù)據(jù)

            另一個(gè)困擾 COM 開(kāi)發(fā)人員的通病是標(biāo)記為 ThreadingModel=Apartment 的進(jìn)程內(nèi)對(duì)象。這項(xiàng)指定告訴 COM,對(duì)象的實(shí)例必須只能在 STA 中創(chuàng)建。它還可讓 COM 自由地將這些對(duì)象實(shí)例放在任何主機(jī)進(jìn)程的 STA 中。

            假設(shè)客戶端應(yīng)用程序有五個(gè) STA 線程,每個(gè)線程都使用 CoCreateInstance 來(lái)創(chuàng)建同一個(gè)對(duì)象的一個(gè)實(shí)例。如果線程是基于 STA 的,且對(duì)象標(biāo)記為 ThreadingModel=Apartment,則這五個(gè)對(duì)象實(shí)例將在對(duì)象創(chuàng)建者的 STA 中創(chuàng)建。因?yàn)槊總€(gè)對(duì)象實(shí)例都在占用其 STA 的線程上運(yùn)行,因此所有五個(gè)對(duì)象實(shí)例都可以并行運(yùn)行。

            到目前為止,一切良好。現(xiàn)在考慮一下,如果這些對(duì)象實(shí)例共享數(shù)據(jù)會(huì)發(fā)生什么情況。因?yàn)閷?duì)象都在并發(fā)線程上執(zhí)行,兩個(gè)或更多的對(duì)象可能會(huì)同時(shí)嘗試訪問(wèn)同一個(gè)數(shù)據(jù)。除非所有這些訪問(wèn)都是讀取訪問(wèn),否則就會(huì)釀成災(zāi)難。問(wèn)題可能不會(huì)很快顯現(xiàn)出來(lái);它們會(huì)以和時(shí)間緊密相關(guān)的錯(cuò)誤形式出現(xiàn),因此很難診斷和重現(xiàn)。這就解釋了以下事實(shí)的原因:ThreadingModel=Apartment 對(duì)象應(yīng)該包括可同步對(duì)共享數(shù)據(jù)的訪問(wèn)的代碼,除非您能夠確定對(duì)象的客戶端不會(huì)對(duì)執(zhí)行訪問(wèn)的方法進(jìn)行重疊調(diào)用。

            問(wèn)題在于,太多的 COM 開(kāi)發(fā)人員相信 ThreadingModel=Apartment 能夠使他們免于編寫線程安全的代碼。事實(shí)并非如此 — 至少不完全如此。ThreadingModel=Apartment 并不意味著對(duì)象必須是完全線程安全的,它代表的是一個(gè)對(duì) COM 的承諾,即訪問(wèn)兩個(gè)或更多對(duì)象實(shí)例共享的數(shù)據(jù)(或此對(duì)象和其他對(duì)象的實(shí)例共享的數(shù)據(jù))時(shí)是以線程安全的方式進(jìn)行的。而提供該線程安全性的任務(wù)應(yīng)該由您,即對(duì)象實(shí)現(xiàn)者來(lái)負(fù)責(zé)。共享數(shù)據(jù)的類型和大小多種多樣,但大多是以全局變量、C++ 類中的靜態(tài)成員變量和函數(shù)中聲明的靜態(tài)變量的形式出現(xiàn)。即使是以下這樣無(wú)害的語(yǔ)句也會(huì)在 STA 中出問(wèn)題:

            static int nCallCount = 0;
            nCallCount++;
            

            因?yàn)檫@個(gè)對(duì)象的所有實(shí)例都將共享一個(gè) nCallCount 實(shí)例,編寫這些語(yǔ)句的正確方式如下:

            static int nCallCount = 0;
            InterlockIncrement (&nCallCount);
            

            注意:您可以使用臨界區(qū)、互鎖函數(shù)或您希望的任何方式,但不要忘了訪問(wèn)基于 STA 的對(duì)象共享的數(shù)據(jù)時(shí)要進(jìn)行同步化!

            返回頁(yè)首返回頁(yè)首

            謹(jǐn)慎啟動(dòng)用戶

            這里還有一個(gè)問(wèn)題讓許多 COM 開(kāi)發(fā)人員都吃過(guò)苦頭。去年春天,有一家公司向我緊急呼救,他們的開(kāi)發(fā)人員使用 COM 構(gòu)建了一個(gè)分布式應(yīng)用程序,其中客戶端進(jìn)程運(yùn)行在與遠(yuǎn)程服務(wù)器的 Singleton 對(duì)象相連接的網(wǎng)絡(luò)工作站上。在測(cè)試過(guò)程中,他們遇到了一些非常奇怪的行為。在一種測(cè)試場(chǎng)景中,客戶端對(duì) CoCreateInstanceEx 的調(diào)用可使它們與 Singleton 對(duì)象正常連接。而在另一個(gè)場(chǎng)景中,對(duì) CoCreateInstanceEx 的相同調(diào)用產(chǎn)生了多個(gè)對(duì)象實(shí)例和多個(gè)服務(wù)器進(jìn)程,使客戶端無(wú)法與同一個(gè)對(duì)象實(shí)例連接,從而實(shí)際影響了應(yīng)用程序。在這兩個(gè)場(chǎng)景中,硬件和軟件是完全相同的。

            此問(wèn)題似乎與安全有關(guān)。當(dāng)處理遠(yuǎn)程激活請(qǐng)求的 COM 服務(wù)控制管理器 (SCM) 在另一臺(tái)機(jī)器上啟動(dòng)一個(gè)進(jìn)程時(shí),它會(huì)為該進(jìn)程分配一個(gè)標(biāo)識(shí)。除非另外指定,它選擇的標(biāo)識(shí)就是啟動(dòng)用戶的標(biāo)識(shí)。換句話說(shuō),分配給服務(wù)器進(jìn)程的標(biāo)識(shí)與啟動(dòng)它的客戶端進(jìn)程的標(biāo)識(shí)相同。在這種情況下,如果 Bob 登錄機(jī)器 A,并使用 CoCreateInstanceEx 連接機(jī)器 B 上的 Singleton 對(duì)象,而 Alice 也在機(jī)器 C 上如法炮制,就會(huì)啟動(dòng)兩個(gè)不同的服務(wù)器進(jìn)程(至少在兩臺(tái)不同的 WinStation 上),實(shí)際上使客戶端無(wú)法再用 Singleton 語(yǔ)義與共享的對(duì)象實(shí)例連接。

            兩個(gè)測(cè)試場(chǎng)景之所以會(huì)產(chǎn)生大相徑庭的結(jié)果,其原因就是在一個(gè)場(chǎng)景(那個(gè)可以工作的場(chǎng)景)中,所有測(cè)試人員都使用只為測(cè)試而設(shè)置的一個(gè)特殊帳戶以同一個(gè)人的身份登錄。而在另一個(gè)場(chǎng)景中,測(cè)試人員都使用他們的普通用戶帳戶登錄。當(dāng)兩個(gè)或更多的客戶端進(jìn)程具有相同標(biāo)識(shí)時(shí),它們可以成功連接到配置為假定啟動(dòng)用戶標(biāo)識(shí)的服務(wù)器進(jìn)程。但是,如果客戶端有不同的標(biāo)識(shí),SCM 會(huì)使用多個(gè)服務(wù)器進(jìn)程(每個(gè)唯一客戶端標(biāo)識(shí)一個(gè))分隔分配給不同對(duì)象實(shí)例的標(biāo)識(shí)。

            wickedfig01.gif

            圖 1 DCOMCNFG 中的用戶帳戶

            找到問(wèn)題以后,解決起來(lái)就很簡(jiǎn)單了:配置 COM 服務(wù)器,讓其使用特定的用戶帳戶而不是假定啟動(dòng)用戶的標(biāo)識(shí)。完成這一任務(wù)的一種方式是在服務(wù)器機(jī)器上運(yùn)行 DCOMCNFG(Microsoft 的 DCOM 配置工具),并將“l(fā)aunching user ”更改為“This user”(請(qǐng)參見(jiàn)圖 1)。如果您喜歡通過(guò)編程方式進(jìn)行更改(可能從安裝程序著手),請(qǐng)?jiān)谥鳈C(jī)注冊(cè)表的 HKEY_CLASSES_ROOT\AppID 部分的 COM 服務(wù)器項(xiàng)中添加 RunAs 值(請(qǐng)參見(jiàn)圖 2)。您還需要使用 LsaStorePrivateData 將 RunAs 帳戶的密碼存儲(chǔ)為 LSA 密鑰,并使用 LsaAddAccountRights 確保帳戶擁有“Logon as batch job”的權(quán)限。(有關(guān)具體操作的示例,請(qǐng)參見(jiàn) Platform SDK 中的 DCOMPERM 示例。請(qǐng)?zhí)貏e注意名為 SetRunAsPassword 和 SetAccountRights 的函數(shù)。)

            返回頁(yè)首返回頁(yè)首

            DCOM 不適于防火墻

            關(guān)于 DCOM 特性和功能的一個(gè)常見(jiàn)問(wèn)題是:“它能跨 Internet 工作嗎?”DCOM 能夠很好地跨 Internet 工作,只要將它配置為使用 TCP 或者 UDP,并且通過(guò)授予任何人啟動(dòng)和訪問(wèn)權(quán)限,可將服務(wù)器配置為允許匿名方法調(diào)用。畢竟,Internet 是一個(gè)巨大的 IP 網(wǎng)絡(luò)。但矛盾的是,如果您將一個(gè)現(xiàn)有的 DCOM 應(yīng)用程序(在公司的內(nèi)部網(wǎng)絡(luò)或 intranet 中工作得很好)改為跨 Internet 工作,它很有可能失敗得很慘。可能是什么原因呢?防火墻。

            DCOM 生來(lái)與防火墻的關(guān)系就如油與水的關(guān)系。原因之一是 COM 的 SCM 使用端口 135 與其他機(jī)器上的 SCM 通信。防火墻限制了它可以使用的端口和協(xié)議,可能會(huì)拒絕通過(guò)端口 135 傳入的通信量。但更大的問(wèn)題在于,為了避免與使用套接字、管道和其他 IPC 機(jī)制的應(yīng)用程序沖突,DCOM 沒(méi)有固定使用特定范圍的端口,相反,它在運(yùn)行時(shí)才選擇所使用的端口。默認(rèn)情況下,它可以使用從 1,024 到 65,535 范圍內(nèi)的任何端口。

            允許 DCOM 應(yīng)用程序通過(guò)防火墻的一種方式是,為 DCOM 要使用的協(xié)議打開(kāi)端口 135 和端口 1,024-65,535。(默認(rèn)情況下,Windows NT 4.0 是 UDP 協(xié)議,Windows 2000 是 TCP 協(xié)議。)但是,這比移除所有防火墻好不了多少。對(duì)此,您公司的 IT 人員可能要發(fā)表意見(jiàn)了。

            另一種更安全和更現(xiàn)實(shí)的解決方案是,限制 DCOM 使用的端口范圍,并只為 DCOM 通信量打開(kāi)一組小范圍端口。根據(jù)實(shí)踐原則,您應(yīng)該為每個(gè)服務(wù)器進(jìn)程分配一個(gè)端口,將連接導(dǎo)出到遠(yuǎn)程 COM 客戶端(不是每個(gè)接口指針一個(gè)端口或每個(gè)對(duì)象一個(gè)端口,而是每個(gè)服務(wù)器進(jìn)程一個(gè))。將 DCOM 配置為使用 TCP 而不是 UDP 是一個(gè)好方法,特別是在服務(wù)器對(duì)其客戶端執(zhí)行回調(diào)時(shí)。

            DCOM 用于遠(yuǎn)程連接的端口范圍和所用的協(xié)議可通過(guò)注冊(cè)表進(jìn)行配置。在 Windows 2000 和 Windows NT 4.0 Service Pack 4 或更高版本上,您可以用 DCOMCNFG 應(yīng)用這些配置更改。以下是將 DCOM 配置為通過(guò)防火墻工作的辦法。

            wickedfig03.gif

            圖 3 選擇協(xié)議

            ?

            在服務(wù)器(在防火墻后寄存遠(yuǎn)程對(duì)象的機(jī)器)上,將 DCOM 配置為使用 TCP 作為其所選協(xié)議,如圖 3 中所示。

            ?

            在服務(wù)器上,限制 DCOM 將使用的端口范圍。記住為每個(gè)服務(wù)器進(jìn)程至少分配一個(gè)端口。圖 4 中的示例將 DCOM 限制為端口 8,192 到 8,195。

            ?

            打開(kāi)您在步驟 2 中選擇的端口,使 TCP 通信量能夠通過(guò)防火墻。同時(shí)打開(kāi)端口 135。

            wickedfig04.gif

            圖 4 選擇端口

            執(zhí)行這些步驟,DCOM 就可以很好地跨防火墻工作了。如果您愿意,SP4 和更高版本還可讓您為單獨(dú)的 COM 服務(wù)器指定終結(jié)點(diǎn)。更多信息,請(qǐng)閱讀 Michael Nelson 關(guān)于 DCOM 和防火墻的優(yōu)秀論文,該論文可在 MSDN Online 站點(diǎn)上找到(請(qǐng)參見(jiàn) http://msdn.microsoft.com/library/en-us/dndcom/html/msdn_dcomfirewall.asp)。

            還應(yīng)注意的是,通過(guò)在服務(wù)器上安裝 Internet 信息服務(wù) (IIS),并使用 COM Internet 服務(wù) (CIS) 通過(guò)端口 80 路由 DCOM 通信量,SP4 和更高版本的用戶還可以使用 CIS 來(lái)提供與防火墻兼容的 DCOM。有關(guān)該主題的更多信息,請(qǐng)參閱 http://msdn.microsoft.com/library/en-us/dndcom/html/cis.asp。

            返回頁(yè)首返回頁(yè)首

            使用線程或異步調(diào)用來(lái)避免 DCOM 超時(shí)設(shè)定太長(zhǎng)

            總是有人問(wèn)我當(dāng) DCOM 無(wú)法完成遠(yuǎn)程實(shí)例化請(qǐng)求或方法調(diào)用時(shí)出現(xiàn)的超時(shí)設(shè)定太長(zhǎng)的問(wèn)題。典型的場(chǎng)景如下:客戶端調(diào)用 CoCreateInstanceEx 來(lái)實(shí)例化遠(yuǎn)程機(jī)器上的一個(gè)對(duì)象,但是這臺(tái)機(jī)器臨時(shí)離線了。在 Windows NT 4.0 上,激活請(qǐng)求不會(huì)立即失敗,DCOM 可能會(huì)花上一分鐘或更長(zhǎng)時(shí)間來(lái)返回失敗的 HRESULT。DCOM 還可能花費(fèi)很長(zhǎng)時(shí)間,使指向已不再存在或其主機(jī)已離線的遠(yuǎn)程對(duì)象的方法調(diào)用失敗。如果可能,開(kāi)發(fā)人員應(yīng)該如何避免這些較長(zhǎng)的超時(shí)設(shè)定呢?

            要回答這個(gè)問(wèn)題,幾句話是講不清楚的。DCOM 高度依賴于基礎(chǔ)網(wǎng)絡(luò)協(xié)議和 RPC 子系統(tǒng)。并沒(méi)有什么神奇的設(shè)置可讓您限制 DCOM 超時(shí)設(shè)定的持續(xù)時(shí)間。但是,我經(jīng)常使用兩種技巧來(lái)避免較長(zhǎng)超時(shí)設(shè)定的負(fù)作用。

            在 Windows 2000 中,當(dāng)調(diào)用在 COM 信道中掛起時(shí),您可以使用異步方法調(diào)用來(lái)釋放調(diào)用線程。(有關(guān)異步方法調(diào)用的介紹,請(qǐng)參 MSDN Magazine 2000 年 4 月刊的“Windows 2000: Asynchronous Method Calls Eliminate the Wait for COM Clients and Servers Alike”。如果異步調(diào)用在合理時(shí)間內(nèi)沒(méi)有返回,您可以通過(guò)調(diào)用用于初始化調(diào)用的調(diào)用對(duì)象上的 ICancelMethodCalls::Cancel 來(lái)取消它。

            Windows NT 4.0 不支持異步方法調(diào)用,甚至在 Windows 2000 中也不支持異步激活請(qǐng)求。怎么解決呢?從背景線程調(diào)用遠(yuǎn)程對(duì)象(或是實(shí)例化該對(duì)象的請(qǐng)求)。使主線程在事件對(duì)象上阻塞,并指定超時(shí)設(shè)定值以反映您愿意等待的時(shí)間長(zhǎng)度。當(dāng)調(diào)用返回時(shí),讓背景線程來(lái)設(shè)置事件。假設(shè)主線程使用 WaitForSingleObject 阻塞,當(dāng) WaitForSingleObject 返回時(shí),返回值可以告訴您,返回是因?yàn)榉椒ㄕ{(diào)用或激活請(qǐng)求返回,還是因?yàn)槟?WaitForSingleObject 調(diào)用中指定的超時(shí)設(shè)定到期。您不能在 Windows NT 4.0 中取消掛起調(diào)用,但是至少主線程可以自由地執(zhí)行自己的任務(wù)。

            圖 5中的代碼演示了基于 Windows NT 4.0 的客戶端如何才能從背景線程調(diào)用對(duì)象。在此示例中,線程 A 封送了一個(gè) IFoo 接口指針,并啟動(dòng)線程 B。線程 B 取消封送了該接口指針,并調(diào)用 IFoo::Bar。無(wú)論調(diào)用返回所花費(fèi)的時(shí)間有多長(zhǎng),線程 A 都不會(huì)阻塞超過(guò) 5 秒鐘,因?yàn)樗?WaitForSingleObject 的第二個(gè)參數(shù)中傳遞的是 5,000 (單位為微秒)。這并不是太好的辦法,但是如果“無(wú)論在線路的另一端發(fā)生什么情況,線程 A 都不會(huì)掛起”這一點(diǎn)很重要的話,忍受這種麻煩也算值得。

            返回頁(yè)首返回頁(yè)首

            共享對(duì)象并不容易

            從我收到的郵件和在會(huì)議上被問(wèn)到的問(wèn)題判斷,困擾許多 COM 程序員的一個(gè)問(wèn)題是如何將兩個(gè)或更多的客戶端與一個(gè)對(duì)象實(shí)例連接。要回答這個(gè)問(wèn)題,寫出長(zhǎng)篇大論(或是一本小冊(cè)子)都很容易,但其實(shí)只要說(shuō)明與現(xiàn)有對(duì)象的連接既不容易也不自動(dòng)化,就足夠了。COM 提供了大量創(chuàng)建對(duì)象的方式,包括很受歡迎的 CoCreateInstance(Ex) 函數(shù)。但是 COM 缺乏一種通用的命名服務(wù),允許使用名稱或 GUID 來(lái)標(biāo)識(shí)對(duì)象實(shí)例。而且它沒(méi)有提供內(nèi)置的方式來(lái)創(chuàng)建對(duì)象,然后將它標(biāo)識(shí)為調(diào)用的目標(biāo)以檢索接口指針。

            這是不是意味著將多個(gè)客戶端與單一對(duì)象實(shí)例連接就不可能了呢?當(dāng)然不是。實(shí)現(xiàn)這一點(diǎn)有五種方式。在這些資源鏈接中,您可以找到更多信息甚至是示例代碼,來(lái)指導(dǎo)您的操作。請(qǐng)注意,這些技術(shù)從一般意義上講不能互換;通常,環(huán)境因素會(huì)決定哪種方式(如果有)適用于手邊的任務(wù): Singleton 對(duì)象 Singleton 對(duì)象就是只實(shí)例化一次的對(duì)象。可能會(huì)有 10 個(gè)客戶端調(diào)用 CoCreateInstance 來(lái)“創(chuàng)建”Singleton 對(duì)象,但實(shí)際上,它們都是接收指向同一對(duì)象的接口指針。ATL COM 類可通過(guò)在其類的聲明中添加 DECLARE_CLASSFACTORY_SINGLETON 語(yǔ)句,來(lái)轉(zhuǎn)換為 Singleton。

            文件名字對(duì)象 如果一個(gè)對(duì)象實(shí)現(xiàn)了 IpersistFile,并在運(yùn)行中對(duì)象表 (ROT) 中使用文件名字對(duì)象(它封裝了傳遞給對(duì)象的 IPersistFile::Load 方法的文件名稱)注冊(cè)了自己,那么客戶端就可以使用文件名字對(duì)象連接對(duì)象的現(xiàn)有實(shí)例了。實(shí)際上,文件名字對(duì)象允許使用文件名稱來(lái)命名對(duì)象實(shí)例,對(duì)象可在這些文件名稱中存儲(chǔ)它們的持久性數(shù)據(jù)。它們甚至能夠跨機(jī)器工作。

            CoMarshalInterface 和 CoUnmarshalInterface 保存接口指針的 COM 客戶端可以與其他客戶端共享這些接口指針,只要它們?cè)敢夥馑椭羔槨OM 為愿意將接口指針?lè)馑徒o同一進(jìn)程中其他線程的線程提供了優(yōu)化(請(qǐng)參見(jiàn)教訓(xùn) 2),但是如果客戶端線程屬于其他進(jìn)程,CoMarshalInterface 和 CoUnmarshalInterface 就是實(shí)現(xiàn)接口共享的關(guān)鍵途徑了。有關(guān)討論和示例代碼,請(qǐng)參閱 MSJ ?? 1999 年 8 月刊中我的超酷代碼專欄。

            自定義類對(duì)象 所有“可以在外部創(chuàng)建的”COM 對(duì)象都伴隨有獨(dú)立對(duì)象,稱為類對(duì)象,它們的作用是創(chuàng)建所謂 COM 對(duì)象的實(shí)例。大多數(shù)類對(duì)象都要實(shí)現(xiàn)一個(gè)名為 IClassFactory 的接口,其中包括一個(gè)可以創(chuàng)建對(duì)象實(shí)例的名為 CreateInstance 的方法。(在底層,COM 將 CoCreateInstance(Ex) 調(diào)用轉(zhuǎn)換為對(duì) IClassFactory::CreateInstance 的調(diào)用)。IClassFactory 的問(wèn)題在于,在檢索指向以前創(chuàng)建的對(duì)象實(shí)例的接口指針時(shí),它一點(diǎn)用處都沒(méi)有。自定義類對(duì)象是實(shí)現(xiàn)替代 IClassFactory 的自定義激活接口的類對(duì)象。由于定義了接口,您還可以定義方法來(lái)檢索對(duì)已由該類對(duì)象創(chuàng)建的對(duì)象的引用。更多信息和以 ATL 編寫的示例,請(qǐng)參閱 1999 年 2 月的超酷代碼專欄。

            自定義名字對(duì)象 1999 年 11 月的超酷代碼專欄介紹了允許使用 C 樣式字符串命名對(duì)象實(shí)例的自定義名字對(duì)象類。將一個(gè)實(shí)例名稱傳遞給 MkParseDisplayName,您就獲得了一個(gè)與對(duì)象實(shí)例連接的名字對(duì)象。這些類型的名字對(duì)象的一個(gè)缺點(diǎn)是,它們?cè)?Windows NT 4.0 中不能跨機(jī)器工作。

            在使用這些方法中的任一個(gè)在客戶端之間共享對(duì)象實(shí)例之前,請(qǐng)問(wèn)自己一個(gè)問(wèn)題:共享是必需的嗎?如果方法調(diào)用從一個(gè)外部數(shù)據(jù)源(數(shù)據(jù)庫(kù)、硬件設(shè)備,甚至可能是全局變量)檢索數(shù)據(jù)以響應(yīng)客戶端的請(qǐng)求,那么為什么不為每個(gè)客戶端分配一個(gè)對(duì)象實(shí)例,并允許每個(gè)實(shí)例訪問(wèn)數(shù)據(jù)源呢?您可能必須同步化對(duì)數(shù)據(jù)源的訪問(wèn),但不一定要使用自定義類對(duì)象、自定義名字對(duì)象等手段。這就是用 Microsoft 事務(wù)服務(wù) (MTS) 構(gòu)建的應(yīng)用程序的工作方式,從各種理由來(lái)看,它已經(jīng)證明是一種引人注目的編程模型,而不僅僅是實(shí)現(xiàn)上的簡(jiǎn)便和性能的改善。

            返回頁(yè)首返回頁(yè)首

            與我聯(lián)系

            您有什么困難的 Win32、MFC、MTS 或 COM(+) 編程問(wèn)題需要解答嗎?如果有,請(qǐng)給我發(fā)郵件,地址是 jeffpro@msn.com。請(qǐng)?jiān)卩]件主題中加上“Wicked Code”(超酷代碼)字樣。請(qǐng)?jiān)彆r(shí)間不允許我個(gè)別回答這些問(wèn)題,但是對(duì)于每個(gè)問(wèn)題,我都會(huì)考慮加入到未來(lái)的專欄內(nèi)容中。

            Jeff ProsiseProgramming Windows with MFC(Microsoft Press,1999 年)一書的作者。他還是 Wintellect(一家軟件咨詢和教育公司)的創(chuàng)辦人之一,該公司的網(wǎng)址是 http://www.wintellect.com

            摘自 MSDN Magazine2000 年 11 月刊。
            此雜志可通過(guò)各地的報(bào)攤購(gòu)買,也可以訂閱

            轉(zhuǎn)到原英文頁(yè)面

            posted on 2006-04-17 16:17 楊粼波 閱讀(261) 評(píng)論(0)  編輯 收藏 引用


            只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


            久久久久久久综合狠狠综合| 久久无码人妻精品一区二区三区| 久久综合噜噜激激的五月天| 亚洲成色www久久网站夜月| 无码久久精品国产亚洲Av影片 | 99久久无色码中文字幕人妻| 少妇久久久久久久久久| 久久精品国产免费一区| 久久久WWW免费人成精品| 国内高清久久久久久| 91视频国产91久久久| 久久亚洲2019中文字幕| 中文字幕乱码人妻无码久久| 情人伊人久久综合亚洲| 欧美日韩精品久久免费| 狠狠狠色丁香婷婷综合久久五月 | 国产精品久久久久久福利69堂| 国产精品99久久久久久猫咪 | 久久久久免费精品国产| 国产亚洲色婷婷久久99精品| 久久天天躁狠狠躁夜夜2020| 色婷婷综合久久久久中文一区二区 | 狠狠久久综合| 久久人人爽人人爽人人片av高请 | 亚洲第一极品精品无码久久 | 国产Av激情久久无码天堂| 久久久久无码中| 国产精品美女久久久| 综合久久一区二区三区| 国产国产成人久久精品| 国产精品国色综合久久| 中文成人无码精品久久久不卡 | 久久精品九九亚洲精品| 亚洲美日韩Av中文字幕无码久久久妻妇| 国产精品免费福利久久| 精品国产99久久久久久麻豆| 久久精品这里只有精99品| 狠狠狠色丁香婷婷综合久久俺| 99久久国产综合精品女同图片| 久久久久久国产精品无码下载 | 久久综合视频网站|