COM實現過程


前言 
COM已經成為一個必需的東西了。在我們周圍,可以說處處充滿了COM – 如果你是在使用WINDOWS,並在其下面編寫程序的話。然而,無論你是用VC,還是使用DELPHI進行COM編程時,在大多數情況下,編程工具的 IDE已經向你隱藏了COM的大部分實現過程,使得程序員根本不需要了解COM,只專心致志地寫其所關心的邏輯代碼。這就意味着,我們很少有機會,能夠揭開COM的神秘面紗,來看到它下面到底是什么東西。這對於一個WINDOWS程序員來說,不能不是個遺憾。 
因此,本文的宗旨,就是拋開現有的IDE提供的各種向導工具,引導大家從最基本的地方入手,完整地從一個空白的程序里,建立起一個COM程序,從而達到能夠比較清晰地了解一個COM,到底是如何生成出來並實現在程序中。 
本文假設,您是一個有COM編程經驗的DELPHI/VC程序員,並希望了解COM的基本實現過程。限於篇幅和時間,我們只討論進程內的COM(DLL)的實現,並引導大家親手建立起一個最簡單的COM程序。 

COM是什么? 
COM有各種表現形式,可以是進程內,也可以是進程外;可以在本機調用,也可以遠程調用。記得國外有個研究COM的組織,他的主題就叫作:COM就是愛! 這當然是外國人的幽默,他只是想說明,COM是個多么重要的東西。那么COM到底是個什么東西呢? 
很早以前,在我剛開始學習COM的時候,身邊就有些程序員告訴我:COM不是DLL,雖然它通常也是以DLL來作為擴展名的,可他完全與DLL完全不同。那么,這種說法是否正確呢?我們來看看,要實現一個進程內的COM,到底需要經過哪些步驟,那么,我們就能很清楚的知道答案了。 
完成一個進程內的COM,通常需要以下幾步: 
1. 建立一個DLL項目,並導出以下四個函數: 
DllGetClassObject, 
DllCanUnloadNow, 
DllRegisterServer, 
DllUnregisterServer; 

2. 定義自定義的接口,同時必須實現Iunknown接口。 
3. 建立GUID,以標識這個組件以及自定義的接口。 
4. 在注冊表中注冊以標記這個DLL。 
大家都看到了,在第一個步驟里,需要建立一個DLL項目,那么,是不是意味着,COM就是一個DLL呢?在這里,我可以明確地告訴大家,從技術上講,一個進程內的COM完全可以被認為就是一個普通的DLL—動態連接庫!如果你拋棄常用的COM API,比如DELPHI中常用的: 
CreateCOMObject()或者 
CreateOLEObject() 
那么您完全可以直接采用加載普通DLL的方式來調用這個COM組件,比如說,您可以直接用LoadLibrary()函數來加載這個DLL,然后使用 GetProcAddress來調用從這個DLL里輸出的接口,從而完成各項操作。這是完全可行的。然而,我不得不告訴大家,把一個COM僅僅看成一個 DLL,那是非常膚淺的看法 – DLL僅僅是一種表現形式而已。更重要的是,COM實現了一種規則。因此我們可以說: 
l COM是一種包含了許多處理邏輯、符合了某種接口規范(如Iunknown規范)的DLL組件。 
(注:如果沒有特別說明,我在本文里所指的COM,都是指進程內的DLL形式的COM) 
l COM實現了Iunknown接口。因此,任何只要符合Iunknown規范,實現了Iunknown接口的DLL組件,我們都可以把他看成是一個COM。 
那么,什么是Iunknown接口呢?如何實現一個Iunknown接口呢?我們看看,在DELPHI中是如何定義一個Iunknown接口的: 
IInterface = interface 
['{00000000-0000-0000-C000-000000000046}'] 
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; 
function _AddRef: Integer; stdcall; 
function _Release: Integer; stdcall; 
end; 
IUnknown = IInterface; 

簡單一點看,我們直接這樣理解就行了: 
IUnknown = interface 
['{00000000-0000-0000-C000-000000000046}'] 
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; 
function _AddRef: Integer; stdcall; 
function _Release: Integer; stdcall; 
end; 
在DELPHI里,interface是編譯器所認識的一種類型。如果是在VC++中,Iunknown將被定義為一種結構(struct)。如果要實現一個Iunknown接口,我們必須用一個類來實現它,比如在DELPHI中,要實現Iunknown接口可以寫成為: 
TMyCOMObject = class (Tobject, Iunknown) 
…… 
end; 
有心的讀者可能會立即問:這個Iunknown接口由Tobject來實現,那么,可不可以是由其他類來實現呢?比如說用Tcomponent類來實現?答案是: 完全可以!! 
例如,我們要實現一個自定義的接口IMyCOM,可以寫成這樣: 
IMyCOMTest = interface(Iunknown); 
TMyCOMTest = class(Tcomponent, IMyCOMTest) 
……. 
End; 
這樣是完全可以的!因為COM關注的只是如何實現一個接口,至於程序員使用什么類來實現,COM是不管的。 
后面我們要實現一個COM的例子,而且我打算就用這個IMyCOMTest接口來做。所以我們把這個接口聲明成為例1,以便后面使用。 

COM的產生 
假如我們已經完成了一個COM,並且已經在系統中注冊了。那么,一個客戶端需要來調用這個COM,這時,系統中發生了哪些事呢? 
一般來說,以DELPHI為例,客戶程序使用CreateCOMObject或者CreateOLEObject調用COM組件時,會發生以下幾個步驟: 
1. CreateCOMObject或者CreateOLEObject的動作。 
我們看看這兩個函數都干了些什么: 

function CreateComObject(const ClassID: TGUID): IUnknown; 
begin 
OleCheck(CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or 
CLSCTX_LOCAL_SERVER, IUnknown, Result)); 
end; 

CreateOLEObject稍微復雜些: 

function CreateOleObject(const ClassName: string): IDispatch; 
var 
ClassID: TCLSID; 
begin 
ClassID := ProgIDToClassID(ClassName); 
OleCheck(CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or 
CLSCTX_LOCAL_SERVER, IDispatch, Result)); 
end; 
看到了嗎?CreateOLEObject多了一個ProgIDToClassID函數。這意味着,如果我們要用CreateOLEObject來調用我們的COM組件,我們將要多一些步驟來編寫我們的COM。這個我將會在后面說明。現在,我們要關注的是CoCreateInstance API函數。 
2. CoCreateInstance API函數將調用CoGetClassObject API,這個調用過程我們是看不到相關的代碼的,因為微軟已經把他封裝好了。而CoGetClassObject函數的作用是什么呢?它將調用 LoadLibrary來尋找我們指定的COM組件(DLL),然后使用GetProcAddress 來尋找組件的入口函數 – 還記得我們上面說過的那四個被導出的函數嗎?對,其中的DllGetClassObject 函數就在這里將被調用。該函數的原形在DELPHI中是: 
function DllGetClassObject(const CLSID, IID: TGUID; var Obj): HResult; 
其中第三個參數:Obj ,將向我們返回COM中的定義的接口。但是,要注意,這個接口並不是我們自定義的接口,而是向我們返回了一個被成為是“類工廠”接口的 IclassFactory的接口。當我們獲得類工廠接口后,就可以獲得我們所需要的、那個我們自定義的接口了。看看IclassFactory 的接口聲明: 
IClassFactory = interface(IUnknown) 
['{00000001-0000-0000-C000-000000000046}'] 
function CreateInstance(const unkOuter: IUnknown; const iid: TIID; 
out obj): HResult; stdcall; 
function LockServer(fLock: BOOL): HResult; stdcall; 
end; 
看到那個CreateInstance 的方法了嗎?對了,它的第三個參數 obj 將向我們返回那個我們定義的接口,比如是我們的IMyCOMTest接口(例1)。這樣,我們就可以調用我們自定義的接口方法了。 
以上的眾多步驟看起來有點讓人迷惑。那么我們就用一個簡單的流程來描繪我們剛才所發生的步驟。不要被那些步驟嚇倒,其實他們是非常簡單的。 
l CreateCOMObject --à CoCreateInstance。 CoCreateInstance 在注冊表中查找COM的注冊信息。 
l CoCreateInstance -à CoGetClassObject 。注冊信息被交給CoGetClassObject。這時候CoGetClassObject將知道COM組件在磁盤上的位置。 
l CoGetClassObject -à LoadLibrary 。LoadLibrary 將尋找COM DLL的入口,然后GetProcAddress調用其輸出函數DllGetClassObject 
l DllGetClassObject 的輸出參數將向我們返回“類工廠”接口IClassFactory。 
l IclassFactory --à CreateInstance 。CreateInstance方法建立其我們實現接口的類。該類將調用自身的QueryInterface 方法,查看用戶指定的接口是否被自己實現,如果實現了,則向返回自定義的接口。 
l 調用結束后,COM客戶將調用COM的DLL輸出函數DllCanUnloadNow 。如果該函數返回S_OK,則釋放該組件。 

實際的COM例子 
下面我們來做一個實際的例子。包括如何建立一個COM Server和一個COM Client。 
對於COM Server,我們將實現以下功能: 
l 單線程,單客戶支持。 
l 實現自定義的接口 
l 能夠使用Regsvr32 在系統中注冊和反注冊。 
l 能夠被DELPHI或者VC++程序調用。 
我們只關注實現最基本的功能。當大家清楚整個流程后,自然就能寫出更高級的功能,比如多線程支持等。 
下面,讓我們開始COM實現之旅。 


COM Server程序 
l 在DELPHI中,新建一個DLL工程。注意是DLL,而不是 Activex Library。並把工程名保存為MyCOM。然后依次建立兩個單元文件: 
MyCOMServer 單元: 此單元描述了COM的邏輯實現 
COMDef 單元: 此單元描述了COM的輸出函數定義。 
l 在MyCOM單元里,我們定義DLL的輸出函數,整個代碼: 

library MyCOM; 

uses 
SysUtils, 
Classes, 
COMDef, 
MyCOMServer in 'MyCOMServer.pas'; 

//在這里導出四個函數。 
exports 
DllGetClassObject, 
DllCanUnloadNow, 
DllRegisterServer, 
DllUnregisterServer; 

{$R *.res} 

begin 
end. 

先做好定義,不要考慮他們是如何實現的。這個在后面我會做詳細解說。在這里我先說明這四個函數的作用: 
DllGetClassObject : 返回類工廠接口。 
DllCanUnloadNow : 告訴客戶�

l 類工廠的實現 
正如我前面所說的,一個類工廠必須去建立我們自定義的接口。在上面,我們定義了自定義的接口,並由類TMyCOMServer 去實現。那么,現在我們還要做的是,實現類工廠,然后由類工廠建立一個TMyCOMServer 的接口實例。類工廠接口定義如下: 
IClassFactory = interface(IUnknown) 
['{00000001-0000-0000-C000-000000000046}'] 
function CreateInstance(const UnkOuter: IUnknown; const IID: TGUID; 
out Obj): HResult; stdcall; 
function LockServer(fLock: Boolean): HResult; stdcall; 
end; 
注意,IclassFactory是系統預先定義了的,在ACTIVEX單元有,所以不需要自己再去定義一次。我們只要去實現它就是: 
TClassFactory = class(TObject, IClassFactory) 
protected 
FLock: integer; 
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; 
function _AddRef: Integer; stdcall; 
function _Release: Integer; stdcall; 
public 
Constructor Create; 
function CreateInstance(const UnkOuter: IUnknown; const IID: TGUID; 
out Obj): HResult; stdcall; 
function LockServer(fLock: Boolean): HResult; stdcall; 
end; 
我們只關注CreateInstance 方法。LockServer 用於在多客戶調用COM時,鎖定COM,以免一個客戶退出時銷毀了COM,那么其他客戶的調用將發生錯誤。但是我們在這里只實現單客戶,所以不考慮這個函數,把他置空就是。 
function TClassFactory.CreateInstance(const UnkOuter: IInterface; 
const IID: TGUID; out Obj): HResult; 
begin 

//我們的自定義接口,就是在這里被創建的。 
MC := TMyCOMServer.Create; 
Pointer(Obj) := Pointer(MC); 
end; 

function TClassFactory.LockServer(fLock: Boolean): HResult; 
begin 

end; 

同樣的,TclassFactory也必須實現引用計數,因為它也實現了Iunknown接口。 
function TClassFactory._AddRef: Integer; 
begin 
Inc(FLock); 

end; 

function TClassFactory._Release: Integer; 
begin 
Dec(FLock); 
if FLock = 0 then 
Free; 
end; 

function TClassFactory.QueryInterface(const IID: TGUID; out Obj): HResult; 
begin 

end; 
其中,QueryInterface 我把它置空,因為在這個例子中,不需要向它查詢什么接口。如果以后讀者需要向它查詢接口時,自己實現相關代碼。 
同樣,在它的構造器中,也預先對計數加1 
constructor TClassFactory.Create; 
begin 
Inc(FLock); 
end; 

到目前為止,我們已經基本實現了一個COM需要的大部分功能。現在,我們需要把它注冊到系統中,以便被其他程序調用。 
l COM的注冊和反注冊 
我們回過頭來,看看如何去實現那四個DLL的輸出函數。這四個函數的原形如下: 
function DllGetClassObject(const CLSID, IID: TGUID; var Obj): HResult;stdcall; 
function DllCanUnloadNow: HResult;stdcall; 
function DllRegisterServer: HResult;stdcall; 
function DllUnregisterServer: HResult;stdcall; 

我們上面所說的類工廠的實例,就是在DllGetClassObject 中創建的。代碼如下: 
function DllGetClassObject(const CLSID, IID: TGUID; var Obj): HResult; 
begin 
CF := TClassFactory.Create; 
Pointer(obj) := Pointer(CF); 
Result := S_OK; 
end; 
同樣的,我們只有一個類工廠,所以可以不理會前面那兩個參數。否則,就要根據不同GUID,來創建不同的類工廠對象。在這里,我們直接把類工廠對象給返回了。 
函數DllCanUnloadNow 用來注銷一個COM。在正常使用中,要根據引用計數,來判斷是否允許用戶注銷。在這里我們直接返回S_OK,讓用戶直接注銷。 
function DllCanUnloadNow: HResult; 
begin 
Result := S_OK; 
end; 
函數DllRegisterServer 用來向注冊表注冊一個COM組件信息。要注冊一個COM,用戶必須知道COM在注冊表中的信息是如何組織的。結構如下: 
HKEY_CLASSES_ROOT 
---- CLSID 
---- GUID 
----- InprocServer32 標明 COM所在磁盤的路徑以及線程模型 
----- ProgID 標明COM所實現的接口 
----- TypeLib 標明 COM 的類型庫的GUID 
----- Version 標明 COM的版本號。 
當發生CreateCOMObject()調用時,輸入參數為COM的CLASS類型的GUID,系統將在注冊表中搜索到相關信息,然后就可以找到該COM的位置,就可以開始調用了。 
注意,如果您希望COM組件支持客戶端的CreateOLEObject()函數的調用,您必須還要注冊一個信息: 
HKEY_CLASSES_ROOT 
----- 接口聲明 
----- CLSID 標明 COM 接口和CLASS類型的GUID的對應關系。 
那么,當發生 CreateOLEObject 調用時,系統將會根據輸入參數(一個COM接口聲明,如a.b),去查找和接口對應的CLASS GUID,然后就可以讀到COM的相關信息了。 
全部代碼如下: 
function DllRegisterServer: HResult; 
var 
lp: pchar; 
ns: Dword; 
begin 
Result := S_FALSE; 
Reg := TRegistry.Create; 
GetMem(lp, 255); 
try 
Reg.RootKey := HKEY_CLASSES_ROOT; 
if Reg.OpenKey('\MyCOM.MyCOMTest',true) then 
begin 
Reg.CreateKey('CLSID'); 
if Reg.OpenKey('CLSID',true) then 
Reg.WriteString('',GUIDToString(Class_MyCOM)); 
end; 
if Reg.OpenKey('\CLSID\' + GUIDToString(Class_MyCOM), true) then 
begin 
if Reg.CreateKey('InprocServer32') = false or 
Reg.CreateKey('ProgID') = false or 
Reg.CreateKey('TypeLib') = false or 
Reg.CreateKey('Version') = false then 
Exit; 
Reg.WriteString('','MyCOM'); 
if Reg.OpenKey('\CLSID\' + GUIDToString(Class_MyCOM) + 
'\InprocServer32', false) then 
begin 
Windows.GetModuleFileName(HInstance,lp, 255); 
Reg.WriteString('', lp); 
Reg.WriteString('ThreadingModel', 'Single'); 
end; 
if Reg.OpenKey('\CLSID\' + GUIDToString(Class_MyCOM) + '\ProgID', false) then 
Reg.WriteString('','MyCOM.MyCOMTest'); 
if Reg.OpenKey('\CLSID\' + GUIDToString(Class_MyCOM) + '\Version', false) then 
Reg.WriteString('','1.0'); 
if Reg.OpenKey('\CLSID\' + GUIDToString(Class_MyCOM) + '\TypeLib', false) then 
Reg.WriteString('',GUIDToString(LIBID_MyCOM)); 

Reg.CloseKey; 
Result := S_OK; 
end; 
finally 
begin 
FreeMem(lp); 
Reg.Free; 
end; 
end; 
end; 

函數DllUnregisterServer 則向系統注銷一個COM的注冊信息。它比較簡單,直接把COM的相關注冊鍵給刪除就是: 
function DllUnRegisterServer: Hresult; 
begin 
Result := S_False; 
Reg := TRegistry.Create; 
try 
Reg.RootKey := HKEY_CLASSES_ROOT; 
Reg.DeleteKey('\CLSID\' + GUIDToString(Class_MyCOM)); 
Reg.CloseKey; 
Finally 
Reg.Free; 
end; 
end;   

最后工作。 
現在,我們編譯程序,然后生成一個DLL文件,在命令行下,使用: 
regsvr32 MyCOM.dll 
向系統注冊COM。 


COM Client程序 
在DELPHI中調用 
新建一個項目,然后在單元中,定義接口信息: 
IMyCOMTest = interface(IUnknown) 
['{D1C4A022-7F6F-42F0-A9B0-4A91703EB124}'] 
function msg: integer;stdcall; 
end; 
定義變量: 
class_wjm: TGUID = '{CE38847E-A386-4753-89F1-34BE80042107}'; 
a: IMyCOMTest; 
然后在窗口的OnCreate 事件里,添加如下代碼: 
procedure TForm1.FormCreate(Sender: TObject); 
begin 
//隨便用哪個都可以 
a := createcomobject(class_wjm) as IMyCOMTest; 
//或者使用 a := createoleobject('MyCOM.MyCOMTest') as IMyCOMTest; 
end; 
然后,放一個按鈕,並在其事件里添加代碼: 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
showmessage(inttostr(a.msg)); 
end; 

在窗口的OnCLose事件里加上: 
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); 
begin 
a := nil; 
end; 
注意一定要釋放接口,否則可能是個難看的 AV 錯誤哦。 
運行我們的程序,點下按鈕,你將看到輸出信息“1978”。 
如果你看不到正確的信息,請仔細查看你的代碼是否和文中一致。你也可以直接向我索要源代碼。 

在VC6中調用 
稍微復雜點。先把GUID翻譯過來: 
//{CE38847E-A386-4753-89F1-34BE80042107}; 
static const CLSID CLSID_MyCOM = {0xCE38847E,0xA386,0x4753, 
{0x89,0xF1,0x34,0xBE,0x80,0x04,0x21,0x07}}; 
//{D1C4A022-7F6F-42F0-A9B0-4A91703EB124} 
static const IID IID_MyCOM = {0xD1C4A022,0x7F6F,0x42F0, 
{0xA9,0xB0,0x4A,0x91,0x70,0x3E,0xB1,0x24}}; 
然后在聲明一次接口的定義: 
struct IMyCOMTest : public IUnknown 

virtual LONG __stdcall msg(); 
}; 
IMyCOMTest* pLink; 
然后放個按鈕上去,並在相關事件里寫代碼: 
void CMyvcView::OnButton6() 

pLink = NULL; 
int a =0; 
CoInitialize(NULL); 
a = CoCreateInstance(CLSID_MyCOM, NULL, 
CLSCTX_INPROC_SERVER,IID_MyCOM, (void**)&pLink); 
if (a==S_OK){ 
LONG a= pLink->msg(); 
}; 

注意,一定要記住調用 CoInitialize(NULL); 這個函數,否則COM無法使用的。 
編譯運行,你應該能看到 a 是等於1978 的。 
總結 
到目前為止,我們成功的編寫了一個最簡單的COM組件,並且在DELPHI和VC中成功調用。這都說明我們的工作是成功的。同時我們也看到,實現一個COM,並不難。 
關於進程外的COM以及DCOM,前者是基於LPC 本地過程調用,后者是基於RPC遠程過程調用。除了協議不同外,其他的都一樣。大家有興趣,可以以后繼續討論。 
關於COM的線程模式,我曾經以為,是COM向導中自動會產生對應的線程代碼,來實現多線程的。但是我后來又認為,根本沒有這回事,COM只是做了個標記,告訴操作系統他的線程模型,至於如何產生線程,則是操作系統做的。有關這方面的討論,還需要進一步研究。 
一個小尾巴 
我們知道,在DELPHI里,有一個Import Type Library 的功能。可以把一個COM組件導到DELPHI中直接使用。但是,如果我們試圖把我們剛才寫的那個組件,也ADD進去的時候,DELPHI會提示: 
加載類型庫/DLL時出錯。 
這是怎么回事呢? 原來,這是MS/BORLAND的一個小花招。我們看看VCL的代碼就知道了,在DELPHI的向導為你創建一個COM時,它偷偷地加了一個 IprovideClassInfo 的接口進去,該接口使用ItypeInfo 接口,主要用於向外提供COM的類型信息的。大家仔細跟下TtypedComObject 這個類,就會發現這個奧秘了。在前例中,我們沒有實現這個接口,那么當然無法被DELPHI加載了。關於如何實現這個接口,已經超出了本文的范圍,所以不於討論。 
有興趣的朋友,可以繼續關注 “COM實現過程(2)”,主要講述如何實現類型庫的。 


2002/6/27 
版權所有 
轉載時請包括作者姓名   

借你寶地,我翻譯了下COM中的TClassInstancing, TThreadingModel,不過只是加點自已的意思,可能沒怎么對,我只是想弄徹底弄明白這兩個東西.你怎么看這兩個東東 

{ Instancing mode for COM classes } 
實例模式 
TClassInstancing = (ciInternal, ciSingleInstance, ciMultiInstance); 
通過COM類廠決定如何實例一個COM對象 

Description 

TClassInstancing說明了類廠確定一個實例化的COM對象加載在進程的空間,並決定單進程中是否創建多個實例,下列是可能是值選項: 

ciInternal 
COM對象是創建在COM服務器中的同一個進程里。這種情況,外部程序不能直接創建一個實例,外部程序的處理必須通過已經創建起來的對象來調用。 
The COM object is created by the same process as the COM server. That is, an external application cannot create an instance of this object directly. Instead, external processes must call a method of the application that creates the document object. 

ciSingleInstance 
在COM對象服務器中只允許一個單獨的實例來響應每一個應用程序。如果這個實例不共享給多層的客戶端(即當有客戶端已經使用了這個實例時),那其它的客戶端需要等待占用實例的客戶釋放實例。 
Allows only a single instance of the COM object for each executable (application). If this single instance is not shared across multiple clients, then each client must launch its own instance of the executable. 

ciMultiInstance 
EXE/DLL中將會創建多個COM對象實例,不同的COM對象實例將響應任何時刻客戶端請求服務 
The COM object is created as one of multiple instances within the same executable. Any time a client requests service, a separate instance of the object gets invoked. 

*********************************************************************************** 
*********************************************************************************** 
*********************************************************************************** 
*********************************************************************************** 

{ Threading model supported by COM classes } 
TThreadingModel = (tmSingle, tmApartment, tmFree, tmBoth, tmNeutral); 
線程模式類型 
線程模式說明了SERVER中是如何連接調用加載COM對象 

Description 
線程模式說明了SERVER中是如何連接調用加載COM對象,應用程序必需確保COM對象實現了自已安全線程模式才行,即說在自已方法和函數中的調用是線程安全的。 


tmSingle 
COM對象由客戶端發出請求,每個COM對象不支持對線程的運作,即每個客戶端請求是一個接一個,連繼的,不能有多線程、多個客戶端的連接的存在,一個客戶端請求完成,下一個客戶端才能請求服務。在服務端中只能有一個COM對象、一個客戶連接的實例。 
COM serializes all client requests. The object does not need to provide thread support. 

tmApartment 
這種類型確保了在同一時刻只能向所有的COM對象中的一個發出一個請求。 
在每一個客戶端實例調用COM對象,服務端將分配一個COM實例給這個客戶端,即COM對象和客戶端實例是一一對應,所以這些實例的數據是線程安全的.而全局的數據變量必須用Critical Section(臨界資源)或相同的功能將它保護起來訪問。(線程變量(threadvar)能夠在這里使用) 
COM ensures that any instance of the COM object services one request at a time. Different objects from the same server can be called on different threads, but each object is called only from that one thread. Instance data is safe, global data must be protected using critical sections or some other form of serialization. The thread抯 local variables are reliable across multiple calls. 

tmFree 
類似於多線程方式,COM對象能夠在任意時刻任意線程進行調用,但必須使用臨界資源(Critical Section或類似)保護所有實例和全局變量中的數據。(線程變量(threadvar)不能夠在這里使用) 
Also called multi-threaded apartment. The COM object can receive calls from any thread at any time. Objects must protect all instance and global data using critical sections or some other form of serialization. Thread local variables are not reliable across multiple calls. 

tmBoth 
身具tmApartment或tmFree的兩種訪問方式。它支持這兩種模式,當客戶通過單線程或Free線程模式訪問時 
Objects can support clients that use either apartment or free threading models. It supports both threading models when clients may be using either single-threaded or free threading models. 

tmNeutral 
分布式客戶能夠在不同的線程中同一時間來訪問對象,但COM對象必須保證調用不沖突,所以你必須預防兩個互斥的線程訪問全局變量數據時的沖突,以及預防所有可通過一個方法、函數來訪問的實例數據。這種對象模式不能夠使用一個用戶接口,僅僅在COM+才有效,如果是COM使用這種模式,將會被映射成 tmApartment模式。 
Multiple clients can call the object on different threads at the same time, but COM ensures that no two calls conflict. You must guard against thread conflicts involving global data and any instance data that is accessed by more than one method. This model should not be used with objects that have a user interface. This model is only available under COM+. Under COM, it is mapped to the Apartment model.

http://blog.csdn.net/diligentcatrich/article/details/7793906


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM