在日常工作中,我看到過許多由不同開發人員編寫的 COM 代碼。我為許多富於創造性的 使用 COM 的工作方式感到驚訝,有一些使 COM 工作的巧妙代碼可能連 Microsoft 都沒有想 到。同樣,看到一些錯誤一次又一次地重犯,使我免不了心灰意懶。這些錯誤很多都與線程 和安全有關,完全不成比例,而這也正是 COM 文檔資料中最缺少的兩個領域。如果不仔細計 劃,它們也是最可能遇到的並可能會絆住您的兩個領域。
本月的“超酷代碼 ”專欄與以前的大多數專欄有所不同。它並未提供一段可在您自己的應用程序中使用的 超酷代碼。相反,它將講述實現基於 COM 的應用程序的正確方式和錯誤方式。它將講述一些 來自艱難實踐的教訓,以及如何避免落入已經讓許多 COM 開發人員吃盡苦頭的陷阱。
在下面的篇幅中,您將讀到八位程序員的記述,這些教訓都來自他們的痛苦經歷。每 個故事都是真實的,但為了保護無辜者,名字都已隱去。我的目的是,通過這些真實的 COM 故事,使您不再重蹈其他 COM 程序員的覆轍。它們還可能會幫助您在編寫的代碼中找出存在 潛在問題的地方。無論情況如何,我想您都會獲得愉快的閱讀體驗。
總是調用 CoInitialize(Ex)
幾個月前,我收到了一封朋友的電子郵件,他就職於一家著名的硬 件公司。他的公司編寫了一個非常復雜的基於 COM 的應用程序,其中使用了許多進程內和本 地(進程外)的 COM 組件。在開始時,應用程序創建了 COM 對象以服務於運行在多線程單 元 (MTA) 中的各種客戶端線程。該對象還可以托管給 MTA,這意味著接口指針可以在客戶端 線程之間自由交換。在測試中,我的朋友發現在應用程序准備關閉之前,一切都進行得不錯 。然後,不知是什麼原因,對 Release 的調用(必須執行此調用,以便正確釋放客戶端占用 的接口指針)被鎖定了。他的問題是:“到底是哪裡出了問題?”
其實答 案非常簡單。應用程序的開發人員其他都做得很對,只有一點例外,而這點又非常重要:他 們沒有在所有的客戶端線程中調用 CoInitialize 或 CoInitializeEx。現代 COM 的基本原 則之一,就是每個使用 COM 的線程都應該先調用 CoInitialize 或 CoInitializeEx 來初始 化 COM。這條原則是無法免除的。除了其他事情以外,CoInitialize(Ex) 應將線程放入單元 中,並初始化重要的每線程狀態信息(這對於 COM 的正確操作是必需的)。調用 CoInitialize(Ex) 失敗通常會在應用程序生命期早期以失敗的 COM API 函數的形式表現出 來,最常見的是激活請求。但有時問題很隱蔽,直到一切都太晚了(例如對 Release 的調用 一去不復返了)才表現出來。當開發小組將 CoInitialize(Ex) 調用添加到所有接觸 COM 的 線程之後,他們的問題就迎刃而解了。
具有諷刺意義的是,Microsoft 竟是 COM 程 序員有時不調用 CoInitialize(Ex) 的原因之一。Microsoft 知識庫中包含的一些文檔中說 ,調用 CoInitialize(Ex) 對基於 MTA 的線程來說不是必需的(有關示例,請參閱文章 Q150777)。是的,在很多情況下,我們可以跳過 CoInitialize(Ex) 而不會出現問題。但是 ,這樣是不應該的,除非您知道自己在干什麼,並且可以絕對肯定自己不會受到負面影響。 調用 CoInitialize(Ex) 是沒有害處的,因此我建議 COM 程序員始終從某個與 COM 相關的 線程中調用它。
不要在線程之間傳遞原始接口指針
我咨詢的首批 COM 項目之 一就涉及到一個包含 100,000 行代碼的分布式應用程序,該程序是由美國西海岸的一個大型 軟件公司編寫的。該應用程序在多個機器上創建了數十個 COM 對象,並從客戶端進程啟動的 背景線程中調用這些對象。開發小組遇到問題了,調用要麼消失得無影無蹤,要麼在沒有明 顯原因的情況下失敗。他們給我演示的最驚人的症狀是:當一個調用無法返回時,在同一台 機器上啟動其他支持 COM 的應用程序(包括 Microsoft Paint 等)會頻繁導致這些應用程 序被鎖定。
檢查他們的代碼後發現,他們違反了 COM 並發的一個基本規則,就是說 ,如果一個線程要與另一個線程共享一個接口指針,它應首先封送該接口指針。如果有必要 ,封送接口指針可使 COM 創建一個新的代理(以及一個新的信道對象,將代理和存根結對) ,以允許從另一個單元向外調用。不通過封送而將原始接口指針(內存中的一個 32 位地址 )傳遞給另一個線程,會繞過 COM 的並發機制,並且如果發送和接收的線程位於不同的單元 中,將出現各種不良行為。(在 Windows 2000 中,由於兩個對象可以共享一個單元,但又 位於不同的上下文中,因此如果線程位於同一個單元中,可能會使您陷入困境。)典型的症 狀包括調用失敗和返回 RPC_E_WRONG_THREAD_ERROR。
Windows NT 4.0 和更高版本可 以使用一對名為 CoMarshalInterThreadInterfaceInStream 和 CoGetInterfaceAndReleaseStream 的 API 函數,在線程之間輕松地封送接口指針。假定您 應用程序中的一個線程(線程 A)創建了一個 COM 對象,繼而接收了一個 IFoo 接口指針, 並且同一進程中的另一個線程(線程 B)想調用這個對象。在准備將接口指針傳遞給線程 B 時,線程 A 應該封送該接口指針,如下所示:
CoMarshalInterThreadInterfaceInStream (IID_IFoo, pFoo, &pStream);
在 CoMarshalInterThreadInterfaceInStream 返回後,線程 B 就可以安全地取消封送該接口指針:
IFoo* pFoo;
CoGetInterfaceAndReleaseStream (pStream, IID_IFoo, (void**) &pFoo);
在這些示例中,pFoo 是一個 IFoo 接口指針,pStream 是一個 IStream 接口指針。COM 在調用 CoMarshalInterThreadInterfaceInStream 時初始化 IStream 接口指針,然後在 CoGetInterfaceAndReleaseStream 內部使用和釋放該接口指針 。實際上,您通常要使用一個事件或其他同步化基元來協調這兩個線程的行為 — 例如 ,讓線程 B 知道接口指針已准備好,可以取消封送。
請注意,以這種方式封送接口 指針不會出現任何問題,因為 COM 有足夠的智能,在不需要進行封送時不會去封送(或重新 封送)指針。如果在線程之間傳遞接口指針時這樣做,使用 COM 就輕松多了。
如果 調用 CoMarshalInterThreadInterfaceInStream 和 CoGetInterfaceAndReleaseStream 看起 來太麻煩,您還可以通過將接口指針放在全局接口表 (GIT) 中,並讓其他線程去那裡檢索它 們,從而實現在線程之間傳遞接口指針。從 GIT 中檢索到的接口指針在被檢索時會自動封送 。更多信息,請參閱 IGlobalInterfaceTable 中的文檔。請注意,GIT 只存在於 Windows NT 4.0 Service Pack 4 及更高版本中。
STA 線程需要消息循環
上一部分中 描述的應用程序還有另一個致命缺陷。看看您是否能指出來。
這個特殊的應用程序恰 好是用 MFC 編寫的。在一開始,它使用了 MFC 的 AfxBeginThread 函數啟動一系列輔助線 程。每個輔助線程要麼調用 CoInitialize 要麼調用 AfxOleInit(MFC 中類似 CoInitialize 的函數)來初始化 COM。某些輔助線程則調用 CoCreateInstance 來創建 COM 對象,並將所返回的接口指針封送到其他輔助線程。從創建這些對象的線程中調用對象將非 常順利,但從其他線程的調用卻從不返回。您知道這是為什麼嗎?
如果您認為問題與 消息循環(或缺少消息循環)相關,那麼答案完全正確。事實確實如此。當一個線程調用 CoInitialize 或 AfxOleInit 時,它是放在單線程單元 (STA) 中。當 COM 創建一個 STA 時,它會創建一個隨附的隱藏窗口。以 STA 中的對象為目標的方法調用將轉換為消息,並放 入與該 STA 關聯的窗口的消息隊列中。當運行在該 STA 中的線程檢索到代表方法調用的消 息時,隱藏窗口的窗口過程就會將消息轉換回方法調用。COM 使用 STA 執行調用序列化。 STA 中的對象一次不可能接收一個以上的調用,因為每個調用都要傳遞給一個而且是惟一一 個運行在對象單元中的線程。
如果基於 STA 的線程無法處理消息會怎麼樣呢?如果 它沒有消息循環又會怎麼樣呢?針對該 STA 中對象的單元間方法調用將不再返回;它們將在 消息隊列中被永遠擱置。MFC 輔助線程中沒有消息循環,因此如果寄宿在這些 STA 中的對象 要從其他單元的客戶端接收方法調用,那麼 MFC 輔助線程和 STA 是配合不好的。
這 個故事的寓意何在呢?STA 線程需要消息循環,除非您肯定它們不會包含要從其他線程調用 的對象。消息循環可以像這樣簡單:
MSG msg;
while (GetMessage (&msg, 0, 0, 0))
DispatchMessage (&msg);
另一種方案是將 COM 線程移到 MTA 中(或者在 Windows 2000 中,移到中立線程單元,即 NTA 中),這裡沒有 消息隊列依賴項。
單元模型對象必須保護共享數據
另一個困擾 COM 開發人員 的通病是標記為 ThreadingModel=Apartment 的進程內對象。這項指定告訴 COM,對象的實 例必須只能在 STA 中創建。它還可讓 COM 自由地將這些對象實例放在任何主機進程的 STA 中。
假設客戶端應用程序有五個 STA 線程,每個線程都使用 CoCreateInstance 來 創建同一個對象的一個實例。如果線程是基於 STA 的,且對象標記為 ThreadingModel=Apartment,則這五個對象實例將在對象創建者的 STA 中創建。因為每個對 象實例都在占用其 STA 的線程上運行,因此所有五個對象實例都可以並行運行。
到 目前為止,一切良好。現在考慮一下,如果這些對象實例共享數據會發生什麼情況。因為對 象都在並發線程上執行,兩個或更多的對象可能會同時嘗試訪問同一個數據。除非所有這些 訪問都是讀取訪問,否則就會釀成災難。問題可能不會很快顯現出來;它們會以和時間緊密 相關的錯誤形式出現,因此很難診斷和重現。這就解釋了以下事實的原因: ThreadingModel=Apartment 對象應該包括可同步對共享數據的訪問的代碼,除非您能夠確定 對象的客戶端不會對執行訪問的方法進行重疊調用。
問題在於,太多的 COM 開發人 員相信 ThreadingModel=Apartment 能夠使他們免於編寫線程安全的代碼。事實並非如此 — 至少不完全如此。ThreadingModel=Apartment 並不意味著對象必須是完全線程安全 的,它代表的是一個對 COM 的承諾,即訪問兩個或更多對象實例共享的數據(或此對象和其 他對象的實例共享的數據)時是以線程安全的方式進行的。而提供該線程安全性的任務應該 由您,即對象實現者來負責。共享數據的類型和大小多種多樣,但大多是以全局變量、C++ 類中的靜態成員變量和函數中聲明的靜態變量的形式出現。即使是以下這樣無害的語句也會 在 STA 中出問題:
static int nCallCount = 0;
nCallCount++;
因為這個對象的所有實例都將共享一個 nCallCount 實例,編寫 這些語句的正確方式如下:
static int nCallCount = 0;
InterlockIncrement (&nCallCount);
注意:您可以使用臨界區、互鎖函數 或您希望的任何方式,但不要忘了訪問基於 STA 的對象共享的數據時要進行同步化!
謹慎啟動用戶
這裡還有一個問題讓許多 COM 開發人員都吃過苦頭。去年春天 ,有一家公司向我緊急呼救,他們的開發人員使用 COM 構建了一個分布式應用程序,其中客 戶端進程運行在與遠程服務器的 Singleton 對象相連接的網絡工作站上。在測試過程中,他 們遇到了一些非常奇怪的行為。在一種測試場景中,客戶端對 CoCreateInstanceEx 的調用 可使它們與 Singleton 對象正常連接。而在另一個場景中,對 CoCreateInstanceEx 的相同 調用產生了多個對象實例和多個服務器進程,使客戶端無法與同一個對象 實例連接,從 而實際影響了應用程序。在這兩個場景中,硬件和軟件是完全相同的。
此問題似乎與 安全有關。當處理遠程激活請求的 COM 服務控制管理器 (SCM) 在另一台機器上啟動一個進 程時,它會為該進程分配一個標識。除非另外指定,它選擇的標識就是啟動用戶的標識。換 句話說,分配給服務器進程的標識與啟動它的客戶端進程的標識相同。在這種情況下,如果 Bob 登錄機器 A,並使用 CoCreateInstanceEx 連接機器 B 上的 Singleton 對象,而 Alice 也在機器 C 上如法炮制,就會啟動兩個不同的服務器進程(至少在兩台不同的 WinStation 上),實際上使客戶端無法再用 Singleton 語義與共享的對象實例連接。
兩個測試場景之所以會產生大相徑庭的結果,其原因就是在一個場景(那個可以工作 的場景)中,所有測試人員都使用只為測試而設置的一個特殊帳戶以同一個人的身份登錄。 而在另一個場景中,測試人員都使用他們的普通用戶帳戶登錄。當兩個或更多的客戶端進程 具有相同標識時,它們可以成功連接到配置為假定啟動用戶標識的服務器進程。但是,如果 客戶端有不同的標識,SCM 會使用多個服務器進程(每個唯一客戶端標識一個)分隔分配給 不同對象實例的標識。
圖 1 DCOMCNFG 中的用戶帳戶
找到問題以後,解決起來就很簡單了:配置 COM 服務器,讓其使用特 定的用戶帳戶而不是假定啟動用戶的標識。完成這一任務的一種方式是在服務器機器上運行 DCOMCNFG(Microsoft 的 DCOM 配置工具),並將“launching user ”更改為 “This user”(請參見圖 1)。如果您喜歡通過編程方式進行更改(可能從安裝 程序著手),請在主機注冊表的 HKEY_CLASSES_ROOT\AppID 部分的 COM 服務器項中添加 RunAs 值(請參見圖 2)。
圖 2 添加 RunAs 值到注冊表中
您還需要使用 LsaStorePrivateData 將 RunAs 帳戶的密碼存儲為 LSA 密鑰,並使用 LsaAddAccountRights 確保帳戶擁有“Logon as batch job”的權 限。(有關具體操作的示例,請參見 Platform SDK 中的 DCOMPERM 示例。請特別注意名為 SetRunAsPassword 和 SetAccountRights 的函數。)
DCOM 不適於防火牆
關 於 DCOM 特性和功能的一個常見問題是:“它能跨 Internet 工作嗎?”DCOM 能 夠很好地跨 Internet 工作,只要將它配置為使用 TCP 或者 UDP,並且通過授予任何人啟動 和訪問權限,可將服務器配置為允許匿名方法調用。畢竟,Internet 是一個巨大的 IP 網絡 。但矛盾的是,如果您將一個現有的 DCOM 應用程序(在公司的內部網絡或 intranet 中工 作得很好)改為跨 Internet 工作,它很有可能失敗得很慘。可能是什麼原因呢?防火牆。
DCOM 生來與防火牆的關系就如油與水的關系。原因之一是 COM 的 SCM 使用端口 135 與其他機器上的 SCM 通信。防火牆限制了它可以使用的端口和協議,可能會拒絕通過端 口 135 傳入的通信量。但更大的問題在於,為了避免與使用套接字、管道和其他 IPC 機制 的應用程序沖突,DCOM 沒有固定使用特定范圍的端口,相反,它在運行時才選擇所使用的端 口。默認情況下,它可以使用從 1,024 到 65,535 范圍內的任何端口。
允許 DCOM 應用程序通過防火牆的一種方式是,為 DCOM 要使用的協議打開端口 135 和端口 1,024- 65,535。(默認情況下,Windows NT 4.0 是 UDP 協議,Windows 2000 是 TCP 協議。)但 是,這比移除所有防火牆好不了多少。對此,您公司的 IT 人員可能要發表意見了。
另一種更安全和更現實的解決方案是,限制 DCOM 使用的端口范圍,並只為 DCOM 通信量打 開一組小范圍端口。根據實踐原則,您應該為每個服務器進程分配一個端口,將連接導出到 遠程 COM 客戶端(不是每個接口指針一個端口或每個對象一個端口,而是每個服務器進程一 個)。將 DCOM 配置為使用 TCP 而不是 UDP 是一個好方法,特別是在服務器對其客戶端執 行回調時。
DCOM 用於遠程連接的端口范圍和所用的協議可通過注冊表進行配置。在 Windows 2000 和 Windows NT 4.0 Service Pack 4 或更高版本上,您可以用 DCOMCNFG 應 用這些配置更改。以下是將 DCOM 配置為通過防火牆工作的辦法。
圖 3 選擇協議
在服務器(在防火牆後寄存遠程對象的機器)上,將 DCOM 配置為使用 TCP 作 為其所選協議,如圖 3 中所示。
在服務器上,限制 DCOM 將使用的端口范圍。記 住為每個服務器進程至少分配一個端口。圖 4 中的示例將 DCOM 限制為端口 8,192 到 8,195。
打開您在步驟 2 中選擇的端口,使 TCP 通信量能夠通過防火牆。同時打 開端口 135。
圖 4 選擇端 口
執行這些步驟,DCOM 就可以很好地跨防火牆工作了。如果您願意,SP4 和更高版 本還可讓您為單獨的 COM 服務器指定終結點。更多信息,請閱讀 Michael Nelson 關於 DCOM 和防火牆的優秀論文,該論文可在 MSDN Online 站點上找到(請參見 http://msdn.microsoft.com/library/en-us/dndcom/html/msdn_dcomfirewall.asp)。
還應注意的是,通過在服務器上安裝 Internet 信息服務 (IIS),並使用 COM Internet 服務 (CIS) 通過端口 80 路由 DCOM 通信量,SP4 和更高版本的用戶還可以使用 CIS 來提供與防火牆兼容的 DCOM。有關該主題的更多信息,請參閱 http://msdn.microsoft.com/library/en-us/dndcom/html/cis.asp。
使用線程或異 步調用來避免 DCOM 超時設定太長
總是有人問我當 DCOM 無法完成遠程實例化請求或 方法調用時出現的超時設定太長的問題。典型的場景如下:客戶端調用 CoCreateInstanceEx 來實例化遠程機器上的一個對象,但是這台機器臨時離線了。在 Windows NT 4.0 上,激活 請求不會立即失敗,DCOM 可能會花上一分鐘或更長時間來返回失敗的 HRESULT。DCOM 還可 能花費很長時間,使指向已不再存在或其主機已離線的遠程對象的方法調用失敗。如果可能 ,開發人員應該如何避免這些較長的超時設定呢?
要回答這個問題,幾句話是講不清 楚的。DCOM 高度依賴於基礎網絡協議和 RPC 子系統。並沒有什麼神奇的設置可讓您限制 DCOM 超時設定的持續時間。但是,我經常使用兩種技巧來避免較長超時設定的負作用。
在 Windows 2000 中,當調用在 COM 信道中掛起時,您可以使用異步方法調用來釋 放調用線程。(有關異步方法調用的介紹,請參 MSDN Magazine 2000 年 4 月刊的 “Windows 2000: Asynchronous Method Calls Eliminate the Wait for COM Clients and Servers Alike”。如果異步調用在合理時間內沒有返回,您可以通過調用用於初 始化調用的調用對象上的 ICancelMethodCalls::Cancel 來取消它。
Windows NT 4.0 不支持異步方法調用,甚至在 Windows 2000 中也不支持異步激活請求。怎麼解決呢?從背 景線程調用遠程對象(或是實例化該對象的請求)。使主線程在事件對象上阻塞,並指定超 時設定值以反映您願意等待的時間長度。當調用返回時,讓背景線程來設置事件。假設主線 程使用 WaitForSingleObject 阻塞,當 WaitForSingleObject 返回時,返回值可以告訴您 ,返回是因為方法調用或激活請求返回,還是因為您在 WaitForSingleObject 調用中指定的 超時設定到期。您不能在 Windows NT 4.0 中取消掛起調用,但是至少主線程可以自由地執 行自己的任務。
下面的代碼演示了基於 Windows NT 4.0 的客戶端如何才能從背景線 程調用對象。//////////////////////////////////////////////////////
// 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 取消封送了該接口指針,並調用 IFoo::Bar。無論調用返回所 花費的時間有多長,線程 A 都不會阻塞超過 5 秒鐘,因為它在 WaitForSingleObject 的第 二個參數中傳遞的是 5,000 (單位為微秒)。這並不是太好的辦法,但是如果“無論 在線路的另一端發生什麼情況,線程 A 都不會掛起”這一點很重要的話,忍受這種麻 煩也算值得。
共享對象並不容易
從我收到的郵件和在會議上被問到的問題判 斷,困擾許多 COM 程序員的一個問題是如何將兩個或更多的客戶端與一個對象實例連接。要 回答這個問題,寫出長篇大論(或是一本小冊子)都很容易,但其實只要說明與現有對象的 連接既不容易也不自動化,就足夠了。COM 提供了大量創建對象的方式,包括很受歡迎的 CoCreateInstance(Ex) 函數。但是 COM 缺乏一種通用的命名服務,允許使用名稱或 GUID 來標識對象實例。而且它沒有提供內置的方式來創建對象,然後將它標識為調用的目標以檢 索接口指針。
這是不是意味著將多個客戶端與單一對象實例連接就不可能了呢?當然 不是。實現這一點有五種方式。在這些資源鏈接中,您可以找到更多信息甚至是示例代碼, 來指導您的操作。請注意,這些技術從一般意義上講不能互換;通常,環境因素會決定哪種 方式(如果有)適用於手邊的任務: Singleton 對象 Singleton 對象就是只實例化一次的 對象。可能會有 10 個客戶端調用 CoCreateInstance 來“創建”Singleton 對 象,但實際上,它們都是接收指向同一對象的接口指針。ATL COM 類可通過在其類的 聲明中添加 DECLARE_CLASSFACTORY_SINGLETON 語句,來轉換為 Singleton。
文件名 字對象 如果一個對象實現了 IpersistFile,並在運行中對象表 (ROT) 中使用文件名字對象 (它封裝了傳遞給對象的 IPersistFile::Load 方法的文件名稱)注冊了自己,那麼客戶端 就可以使用文件名字對象連接對象的現有實例了。實際上,文件名字對象允許使用文件名稱 來命名對象實例,對象可在這些文件名稱中存儲它們的持久性數據。它們甚至能夠跨機器工 作。
CoMarshalInterface 和 CoUnmarshalInterface 保存接口指針的 COM 客戶端可 以與其他客戶端共享這些接口指針,只要它們願意封送指針。COM 為願意將接口指針封送給 同一進程中其他線程的線程提供了優化(請參見教訓 2),但是如果客戶端線程屬於其他進 程,CoMarshalInterface 和 CoUnmarshalInterface 就是實現接口共享的關鍵途徑了。有關 討論和示例代碼,請參閱 MSJ 1999 年 8 月刊中我的超酷代碼專欄。
自定義類對象 所有“可以在外部創建的”COM 對象都伴隨有獨立對象,稱為類對象,它們的作 用是創建所謂 COM 對象的實例。大多數類對象都要實現一個名為 IClassFactory 的接口, 其中包括一個可以創建對象實例的名為 CreateInstance 的方法。(在底層,COM 將 CoCreateInstance(Ex) 調用轉換為對 IClassFactory::CreateInstance 的調用)。 IClassFactory 的問題在於,在檢索指向以前創建的對象實例的接口指針時,它一點用處都 沒有。自定義類對象是實現替代 IClassFactory 的自定義激活接口的類對象。由於定義了接 口,您還可以定義方法來檢索對已由該類對象創建的對象的引用。更多信息和以 ATL 編寫的 示例,請參閱 1999 年 2 月的超酷代碼專欄。
自定義名字對象 1999 年 11 月的超 酷代碼專欄介紹了允許使用 C 樣式字符串命名對象實例的自定義名字對象類。將一個實例名 稱傳遞給 MkParseDisplayName,您就獲得了一個與對象實例連接的名字對象。這些類型的名 字對象的一個缺點是,它們在 Windows NT 4.0 中不能跨機器工作。
在使用這些方法 中的任一個在客戶端之間共享對象實例之前,請問自己一個問題:共享是必需的嗎?如果方 法調用從一個外部數據源(數據庫、硬件設備,甚至可能是全局變量)檢索數據以響應客戶 端的請求,那麼為什麼不為每個客戶端分配一個對象實例,並允許每個實例訪問數據源呢? 您可能必須同步化對數據源的訪問,但不一定要使用自定義類對象、自定義名字對象等手段 。這就是用 Microsoft 事務服務 (MTS) 構建的應用程序的工作方式,從各種理由來看,它 已經證明是一種引人注目的編程模型,而不僅僅是實現上的簡便和性能的改善。