Delphi COM编程技术二(COM接口和COM组件)

一、类厂(Class Factory)

1、类厂的含义:

    类厂是一种组件或者对象,它就是用于创建其他对象的。COM对象不是由程序直接实例的,COM使用类厂来创建对象。每一个COM都有一个相关的类厂,负责创建在服务器中实现的COM对象;类厂把COM从实际构造一个对象的过程中分离出来,COM没有对象构造过程,所以需要使用类厂来解决创建组件时的不灵活性。

 

2、类厂的创建和COM组件的创建:

    COM对象的创建可以通过CoCreateInstance()函数来完成。在使用时只需将CLSID、IID等作为参数传入即可创建相应的组件并从输出参数ppv得到所请求接口的指针。如果函数是直接创建组件的,那么在函数返回时组件将创建完毕,这样客户将无法对组件的创建过程进行任何干预,灵活性太差。因此,CoCreateInstance()函数内部实现中通过调用CoGetClassObject()函数先创建一个类厂(一种专门用来创建组件的组件)来解决此问题。

1>、CoCreateInstance函数:创建CLSID相应的组件并从输出参数ppv得到所请求接口的指针
    CoCreateInstance内部创建负责创建COM对象类厂的实例,然后使用类厂来创建对象,创建完后COM对象后,类厂被销毁;如果使用CoCreateInstance函数创建了一个借口,你必须使用Release()函数释放掉它。
HRESULT __stdcall CoCreateInstace(
 const CLSID& clsid,  // 要创建COM组件的CLSID
 IUnknown* pIUnknownOuter,  // 用于聚合组件,如果不使用可以设置为NULL
 DWORD dwClsContext, // 限定了所创建组件的执行上下文
 const IID& iid,  // COM组件中要使用的接口的IID
 void** ppv  // 返回得到的接口指针
);

参数说明:

CLSID:它可唯一指定客户端需要使用的COM类。COM将使用该ID来查找可产生请求COM对象的服务器。一旦连接到服务器,将会创建该对象。
pIUnknownOuter:它指向“outer unknown”。我们不会使用这个参数,因此可以传送一个NULL。在涉及到“aggregation”(集合)概念时,outer unknown是很重要的。aggregation可让一个接口直接调用另一个COM接口而无需通知客户端。aggregation和containment是接口用来调用其它接口的两个方法。
dwClsContext:定义COM类的Context或者CLSCTX。该参数控制服务器的范围。我们可以通过它来控制服务器是进程内的服务器,还是一个EXE或者是在远程的计算机上。CLSCTX是一个位掩码,因此你可以混合几个值。(CLSCTX_INPROC_SERVER--该服务器将运行在本地的计算机,并且作为一个DLL连接到客户,进程内的服务器是最容易实现的)。
IID:请求的接口的ID。

PPV:指向一个接口的指针 

2>、CoGetClassObject函数:创建类厂对象并返回类厂指针
STDAPI CoGetClassObject(
    REFCLSID rclsid,    
    DWORD dwClsContext,
    LPVOID pvReserved,  
    REFIID riid,    
    LPVOID * ppv    
 );

    创建类厂的CoGetClassObject()函数将接收一个CLSID作为参数并返回指向类厂对象IClassFactory接口的指针。客户将可以通过此指针来创建所需要的组件并返回某接口的指针(IUnknown接口指针)。通过此指针,客户将可以直接调用新创建的COM对象接口的成员函数,从而获得COM对象的所有服务。

 

3、IClassFactory接口:用于创建组件的接口

    类厂所支持IClassFactory接口,该接口从IUnknown派生,并具只定义了两个接口成员函数CreateInstance()函数和LockServer()函数。这两个成员函数分别用于创建COM组件对象和控制组件的生存期。

1>、CreateInstance成员函数:负责创建类厂涉及到的COM对象的实例,并返回IUnknown接口指针;
HRESULT __stdcall CreateInstance(IUnknown* pIUnknownOuter, const IID& iid, void** ppv);

    这个用于创建组件对象的CreateInstance()函数并未包含一个用来接受CLSID的参数,显然该函数将只能创建同某个CLSID相应的组件。而对于一个类厂,也没有其他函数用于创建组件,因此只能用CreateInstance()函数创建与某个特定CLSID(CoGetClassObject函数接受到的CLSID)相应的组件。


2>、LockServer成员函数: 控制组件的生存期,保持服务器在内存中,一般不要调用它。

 

二、创建类厂对象的两种情况

1、进程内的COM服务器(In-Process COM Server)

    如果COM对象是进程内组件,即组件与客户处于同一进程地址空间,通常多以DLL形式存在。CoGetClassObject()将调用DLL模块的DllGetClassObject()函数并把clsid、iid和ppv等参数传递进去以创建类厂,并返回类厂对象的接口指针。
    共性:有一个InprocServer32的子键、所有进程内服务器都输出以下四个标准函数; 
1、DllRegisterServer: 2种方式自动调用IDE的Register ActiveX Server菜单

  Windows的命令行应用程序RegSvr32.exe或Boland应用程序TRegSvr 
2、DllUnregisterServer: 是DllRegisterServer的逆进程,移走放在注册表中的条目; 
3、DllGetClassObject: 负责给COM创建一个类厂,该类厂用语创建一个COM对象; 每个COM服务器将实现它输出的每个COM对象的类厂; 
4、DllCanUnloadNow: 返回S_True,CO在内存中移走COM服务器;如果返回S_False 


2、进程外COM服务器(Out-Of-Process COM Server)

    如果COM对象是进程外组件(拥有独立的进程地址空间,通常多以EXE形式实现的),则CoGetClassObject()将要首先启动组件进程,并一直等待到组件进程通过CoRegisterClassObject()函数将类厂注册到COM后,才会返回COM中相应的类厂信息。一旦组件进程退出,此注册的类厂对象也就不再有效,需调用CoRevokeClassObject()函数予以通知。

 

3、各类名称的含义
线程支持(Threading Support): 只适用于进程内服务器;被保存在注册表中。
Single-Thread Apartment(STA):是线程模型, 实际上根本没有线程支持,所有对COM服务器的访问由Windows顺序执行,不必考虑同步存取的问题。 
Mutli-Threaded Apartment(MTA): 是线程模型,允许同时有多个线程存取COM对象,必须控制不同线程同步存取的程序代码。 
Both Apartment: 可以执行在MTS或STA中;  
注册服务器(registering the Server): 所有的COM服务器都需要Windows注册表来工作。 
定制构造函数(Custom constructors): delphi中COM对象的基类包括一系列的非虚构造函数。 为了能够使用COM库提供的API函数,首先要用CoInitialize()初始化COM库,不要试图重载一个COM对象的构造函数; 
实例化(Instancing): 创建多少个客户需要的实例;可以支持三个实例化方法中的一个; 
单实例(Single Instace): 只容许一个COM对象的实例,每个需要COM对象实例的应用程序将产生COM服务器的单独拷贝; 
多实例(Multiple Instance): 指COM Server可以创建一个COM对象的多个拷贝;客户请求COM对象的一个实例时,由当前运行的服务器创建COM对象的一个实例; 
内部实例(Internal Only):用于不被客户应用程序使用的COM对象; 
调度数据(Marshaling Data): 一个可执行的程序不能直接访问另一个可执行程序的地址空间;Windows通过调度(Marshaling)进程在调用应用程序和进程外COM服务器之间移动数据; 自动化兼容 SmallInt、Integer、Single、Double、currency、TDateTime、WideString、Idispatch、Scode、WordBool、Olevariant、IUnknown、ShortInt、Byte; 记录和数组不能自动调度;

三、总结COM组件的创建和使用过程

1、使用CLSIDFromProgID()函数,根据组件的ProgID得到组件的CLSID。

    虽然通过CLSID和ProgID都可以标识一个组件,但ProgID显然要比CLSID更易于理解和使用,因此通常很少直接使用CLSID,而是通过使用CLSIDFromProgID(),根据ProgID得到组件的CLSID。

2、调用CoGetClassObject()函数,把刚得到的CLSID作为参数去创建类厂对象并返回类厂接口指针。

3、再通过类厂接口指针调用类厂对象的CreateInstance()接口成员函数,执行结果将创建与CLSID相应的组件对象并返回IUnknown接口指针。

4、通过IUnknown接口指针的QueryInterface()成员函数取组件的其他接口指针,从而为组件提供的各种服务。
5、最后,通过Release()函数释放接口指针。

(如果使用的进程内组件,在调用CoUninitialize()函数释放COM库资源之前,应首先调用CoFreeUnusedLibraries()将其从内存卸载。由于在CoCreateInstance()函数内部实现了对CoGetClassObject()的调用。)

 

四、客户端和服务端的交互

1、传统的C/S模式和COM的C/S模式之间的对比

概念

传统的(C++/OOP)

COM

客户端

一个从某个服务器请求服务的程序

一个调用COM方法的程序

服务器

一个为其它程序服务的程序

一个让某个COM客户得到COM对象的程序

接口

没有

通过COM调用的一组函数的一个指示器

一个数据类型

定义了一组一起使用的方法和数据,一个对象的定义,用来实现一个或者多个COM接口,“coclass”也是

对象

一个类的实例化

一个coclass的实例化

Marshalling

没有

在客户和服务器端之间移动数据

 

2、COM客户端和COM服务端的交互

    在COM中,客户端程序驱动所有的事情,服务器是被动的,只响应客户的请求。这意味着对于客户的个别方法调用,COM服务器以一个同步的方式运作。
 1>、客户端的程序启动服务器
 2>、客户端请求COM对象和接口
 3>、客户端发起所有的方法调用到服务器
  4>、客户端释放服务器的接口,允许服务器关闭
  这个区别是重要的。有各种不同的方法可模拟服务器到客户的调用,不过它们都难以实现,并且都相当复杂(这个被称为回叫)。通常没有客户的请求,服务器不做任何的事情。

3、一个典型的COM客户端和COM服务器之间的交互
  客户请求:

    请求访问一个特别的COM接口,特别的COM类和接口(通过GUID)
  服务器响应:

    1>、启动服务器(如果需要)。
       (如果是一个进程内的服务器,DLL将被载入。可执行的服务器将由SCM运行.)。
    2>、创建请求的COM对象。
    3>、创建到COM对象的一个接口。
    4>、增加激活接口的引用计数。
    5>、返回该接口给客户。
  客户请求 :
    调用接口的一个方法。
  服务器响应:

    执行一个COM对象的方法。
  客户请求:
    释放接口。
  服务器响应:
    减少接口引用的数目;如果引用计数为0,将会删除该COM对象;如果没有活动的连接,关闭服务器。某些服务器不会关闭自身。(如果你要了解COM,你必须使用一个以客户为中心的方法。)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值