COM实现过程 
原创:吴剑明(foxnt) 前言 
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 : 告诉客户

解决方案 »

  1.   


    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; 
      

  2.   

    l 最后工作。 
    现在,我们编译程序,然后生成一个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 
    版权所有 
    转载时请包括作者姓名 
     
      

  3.   

    COM实现过程(2)何时贴出来?
      

  4.   

    借你宝地,我翻译了下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.
      

  5.   

    说实在的,这问题想问很久,但有时,总不知如何表达才对。一直前段,得,翻译它得了。不过译归译,有些语句不通,所以想来请教  COM加载到内存,按Delphi来分: 
        ciInternal, ciSingleInstance, ciMultiInstance
      我想能否用更通俗的的来说清楚它,如ciInteranl,我翻译的我自已也看不懂,别说给人看了。
      我是说能否即兴举个例子来,比如ADO这COM按这样分类属什么?如果以后我写COM,那我应该是写ciInteranl,还是ciSingleInstance,或者来个经验谈也行。  线程模式今早再看看,发现已经明白了。:)  BTW:你写COM时会这样分清楚TClassInstancing、TThreadingModel,有时COM是机器的,你是怎么写的,如MTS,DCOM之流
      

  6.   

    -------------------------------------
    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.
    ------------------------------------------
    这里的COM对象,你应该理解成COM中的接口对象。这种模式下的接口对象不能被外部EXE调用CoCreateInstance来创建的,只能在COM SERVER里,自己创建。  其他两个好理解吧?ADO显然是ciMultiInstance的。》》BTW:你写COM时会这样分清楚TClassInstancing、TThreadingModel,有》》时COM是机器的,你是怎么写的,如MTS,DCOM之流   这句话没有看明白你的意思。
      

  7.   

    》》ciInteranl只能在COM SERVER里,自己创建COM Server?怎么理解?我写一个ciInteranl的COM接口,那Com Server是谁?自已创建好像有点不可理解。不会说你写COM接口时,顺便将调用它的接口或函数之类的东东也写好,也作为一个COM接口来使用吧?那句我是想问:你写COM接口时,会将它的TClassInstancing、TThreadingModel分清楚不,因为有些COM是MTS或DCOM,它们是需要远程创建的。如COM = CreateRemoteComObject(Machine, GUID),对这些COM,它们的TClassInstancing、TThreadingModel,说说你的经验和心得。哪些适合哪。
      
    还有,我想写COM最常见的遇到的问题就是“interface not support",不知你下篇文章能否介绍点一些写COM时一些常见问题解决的方法不。
      

  8.   

    >>COM Server?怎么理解?
    指你的APPLICATION。比如一个进程外的EXE服务器。对于DCOM/MTS,对实例和线程模型没有特别的要求。就是和本地的COM一样了。注意DCOM是指COM和COM之间的通信协议,而不是什么特别的COM。》》interface not support
      当然就是你访问了不存在的接口啦。常见的原因还有忘记注册COM或者忘记注册它的类型库(如果是DCOM)。
      

  9.   

    如你所说:
      我的COM SERVER(application)放在Server端,它作为一个中间层提供数据访问功能,它里面有XX个COM接口,它们是属于ciInternal,COM接口只能在Server端中的Application中被Create, invoke, destroy,那客户端怎么访问COM接口的procedure/function/property ? Socket? DCOM?不明白
      

  10.   

    那就不能访问这些接口了呀。这些接口只能被COM本身内部代码去访问了。
      

  11.   

    to foxnt(天马幻想) :
      顺便问一下,我在别的机器上注册了此com,但在我机器上调用怎么不行,而且我还用
    a := CreateRemoteComObject('其他机器',Class_wjm) as IMyCOMTest;
      

  12.   

    DCOM的访问是需要权限的。你要在DCOMCFG。EXE里配置。
      

  13.   

    挺不错的!
    COM实现过程(二)什么时候能看到?
      

  14.   

    我是一个Dephi初学者,不过我对Com特别感兴趣,在学习中有很多不懂,请大家多多帮忙!谢谢!
      

  15.   

    收藏!
    Foxnt不是版主吗, 为什么是两个三角?
      

  16.   

    建议大家都买本<delphi com深入编程>
    上面写得很清楚是一本很不错的书