COM技术内幕摘要


InProcSever32进程内COM服务器
LocalSever32进程外COM服务器

InProcSever:
delphi创建进程内com服务器的过程:
CreateComObject(const ClassID:TGUID):IUnknown
其ClassID是接口类的CLSID,在此函数内部会调用CoCreateInstance,CoCreateInstance封装了CoGetClassObject.
CoCreateInstance:
Creates a single uninitialized object of the class associated with a specified CLSID.
Call CoCreateInstance when you want to create only one object on the local system.
To create a single object on a remote system, call CoCreateInstanceEx.
To create multiple objects based on a single CLSID, refer to the CoGetClassObject function.

It is convenient to use CoCreateInstance when you need to create only a single instance of
an object on the local machine. If you are creating an instance on remote machine,
 call CoCreateInstanceEx. When you are creating multiple instances,
it is more efficient to obtain a pointer to the class object's IClassFactory interface
and use its methods as needed. In the latter case, you should use the CoGetClassObject function.

CoCreateInstance内部调用CoGetClassObject,搜索注册标,获得该COM类
所在的DLL位置并加载,通过调用在该DLL导出的函数获DllGetClassObject得接口类的Class Factory,
通过调用该类厂的的方法CreateInstance最终获得需要的接口指针。
如上英文所注:当要创建一个COM类的多个实例时,应该直接调用CoGetClassObject获得类厂指针,然后
调用类厂的方法CreateInstance创建接口。
CreateComObject-->CoCreateInstance-->CoGetClassObject--->DllGetClassObject---->CreateInstance

LocalSever32
delphi创建进程外COM服务器的过程:
CreateComObject(const ClassID:TGUID):IUnknown
其ClassID是接口类的CLSID,
CreateComObject-->CoCreateInstance--->CoGetClassObject--->CreateProcess-->CoRegisterClassObject--->CreateInstance

聚合:
一般来说,要重用一个delphi 对象或C++对象可以通过继承或组合的方式,那么要冲用COM对象(实现了接口的COM类),则是通过
聚合的方式,有点类似普通对象的组合,但不同的是,聚合类必须包装被聚合类的接口方法,当客户调用聚合类引出的包装的
方法时,聚合类将客户调用转入被聚合的类的相应方法。


DCOM:
CreateRemoteComObject-->CoCreateInstanceEx-->CoGetClassObject--CreateInstance;

自动化:
本质上是实现了IDispatch接口的COM对象。自动化的例子,比如在word中可以编辑Excel表格,可以编辑图片等。
它是一种应用程序或DLL暴漏可编程控制对象给其他应用程序的技术。提供这种对象的应用叫自动化服务器,
访问这种对象的应用叫自动化控制器。自动化最大的优势是语言无关。一个自动化控制器可以控制任何一门
语言开发的自动化服务器。

type
   TButtonEdit = class(TWinControl)
   private
    { Private declarations }
   protected
    { Protected declarations }
   public
      { Public declarations }
   published
     { Published declarations }
   end;


COM把类的实现和声明分离开来。
接口是声明,
coClass是其实现,包含在dll(in-process),exe(out-process)中。
COM对象在内存中表现为coClass 的一个实例。
COM服务器则是包含了一个或多个coClass的二进制文件(dll或exe).
Registration是创建注册表入口,告诉Windows,COM服务器放在什么位置。
coClass的GUID叫做CLSID,Interface的GUID叫做IID.
HRESULT是COM用来返回错误和成功的整型数字。没有其他意义,不代表句柄
 
使用coCreateInstance,API函数创建COM对象。
此Api通过COM对象的类工厂来创建实例。
类工厂是特殊的COM对象,支持IClassFactory接口。
IClassFactory接口实现了两个方法:
 CreateInstance,LockServer.
 CreateInstance创建类工厂所关联的COM对象的实例
 LockServer作用是把COM服务器锁定在内存中,即使已经没有客户应用它。


MFC和ATL都可以开发ActiveX控件
ActiveX控件是基于COM技术,其基本原则是对象的接口和实现分开。
ActiveX控件的客户通过接口驱动它。
一个ActiveX控件是实现了:
  引入COM接口;
  OLE嵌入协议
  连接点和属性页
这几个主要ActiveX技术的COM对象

从低层次来说,ActiveX控件是实现了某些类型接口的COM类。当客户能查询到
这些接口时,就可以使用它了

ActiveX控件的三类接口:
  1. 可嵌入对象
  即它实现了多数OLE文档,in-place激活和嵌入协议。
  IOleObject,IPersistStorage,IDataObject,IOleInPlaceActiveObject,
  IOleInPlaceObject,IViewObject2,IRunnableObject
  2. 支持属性页
  客户可以修改属性
  3. COM的连接点技术
  客户可以发现外出接口

MFC对COM的支持
   MFC通过加入一些虚函数到CcmdTarget类中和一些宏中实现了对COM的支持
   CcmdTarget类:
   实现了Iunknown接口,
   成员变量m_dwRef,用于引用计数
   用于实现IUnkonwn的六个函数:InternalAddRef,InternalRelease,InternalQueryInterface,
                               ExternalAddRef,ExternalRelease,ExternalQueryInterface.
  InternalAddRef,InternalRelease,InternalQueryInterface完成引用计数和接口查询
  ExternalAddRef,ExternalRelease,ExternalQueryInterface代理控制聚合的对象

  MFC使用嵌套的类复合策略实现COM,在MFC中,想实现COM接口的类从CcmdTarget中派生。
  这些继承类实现的接口的到自己的嵌套类,MFC使用宏BEGIN_INTERFACE_PART和
  END_INTERFACE_PART来产生嵌套类

  MFC的QueryInterface是表格驱动,其接口映射和它的消息映射基本相同:
  消息映射把一个Windows消息和C++类中的函数关联,
  接口映射把一个接口的GUID和一个表示接口的特定vptr的地址相联系。
  每个基于CcmdTarget的类实现COM接口通过更多的宏:
  DECLARE_INTERFACE_MAP,BEGIN_INTERFACE_MAP,INTERFACE_PART,和END_INTERFACE_MAP来增加一个
  接口映射(类似于消息映射的几个宏)

COM组件是以Win32动态链接库(DLLS)或可执行文件(EXE)的形式发布的可执行代码组成的。
COM组件:
   与语言无关
   以二进制形式发布
   可自由升级
   可透明的在网络上重新分配

开发COM的目的是为了使应用程序更易于定制,更为灵活。
在Win32SDK中OBJBASE.h中定义了 #define interface struct
                              #define STDMETHODCALLTYPE _stdcall
在          WINDEF.H中定义了  #define pascal _stdcall
_stdcall是微软对编译器的扩展被此标记标识的函数将在返回到调用者时会把参数从栈中删除
_cdecl是常规C/C++调用约定,由调用者清理参数

组件是接口的集合,接口是函数的集合
接口的不变性,接口一旦公布,就永不能该变
所有的COM接口都必须支持一个IUnknown接口,定义与UNKNWN.H头文件中。此接口包含三个函数。所以组件的任何一个接口都可以被客户用来获取它所支持的其它接口,所以任何一个接口指针都可以被当作IUnknown接口指针,客户通过IUnknown *CreateInstance()来获得IUnknown接口指针,然后可以通过调用QueryInterface函数询问组件是否支持某个特定接口,若支持,则QueryInterface返回指向该接口的指针,否则返回一个错误码
HRESULT _stdcall QueryInterface(const IID& iid, void **ppv);
因为接口实质是一个函数指针,所以保存接口需要指向指针的指针
QueryInterace查询IUnknown接口时,无论从哪个接口指针调用的QueryInteface,其返回的IUnknown接口指针都是相同的,即指向同一个地址。因此当判断连个接口是否指向同一组件的时候,可以从这两个接口返回IUnknown接口指针进行比较
对QueryInterface而言一个IID就是一个接口
STDAPI DllRegisterServer();
STDAPI DLLUnregisterServer();

STDAPI 在OBJBASE.h中定义为 #define STDAPI EXTERN_C HRESULT STDAPICALLTYPE
展开后是extern "C" HRESULT _stdcall

组件类别:
  一个组件类别实际桑就是一个接口集合,该集合被分配一个GUID,叫做CATID,对于某个组建而言,它若实现了某个组建类别的所有接口,那么就可以将其注册成该组件类别的一个成员.客户就能够通过注册表选择只属于某个特定组建类别的组件而准确的找到它所需的组件.
   用途一:指定某个组件必须实现的接口集合.
   用途二:指定独见需要其客户提供的接口集合(由于某些组建需要客户提供某些服务才能正常运行)

Component Category Manager提供了对组建类别的管理可以完成将某个组件加入到某个组件类别中,也可将其从中删除。

COM库:
所有的COM组件和客户都需要完成一些相同的操作,为保证这些操所是按照标准的并且兼容的方法完成的,COM定义了一个函数库以实现所有这些操做
此函数库在OLE32.DLL中实现,
在使用COM库中的函数(除CoBuildVersion外),进程必须先调用CoInitialize来初始化COM库函数,当进程不再需要时,必须调用CoUninitialize.
原形:HRESULT CoInitialize(void *reserver) //参数设为NULL
      void    CoUninitialzie();

内存管理
  问:  在组件中分配的内存当作为参数返回给客户时,该如何释放?
  答:使用COM提供的任务内存分配器,通过IMalloc接口调用。此接口由CoGetMalloc
返回。分配内存可以使用IMalloc::Alloc,释放可由IMalloc::Free完成。
为了简化,COM库提供了
  void *CoTaskMemAlloc(ULONGl cb) //参数为欲分配内存的大小
  void CoTaseMemFree(void *pv)

ProgID是设计组件程序员为组件定义的容易记忆的名字,其主要作用是获取CLSID
可以用CLSIDFromProgID和ProgIDFromCLSID

注册表中的CLSID是字符中表示的,为了将其转化为GUID可以使用
   StringFromGUID2,需要一个Unicode串.在非Unicode系统中,需要将其结果
转化为单字节字符串,为此使用wcstombs函数
例如:
  wchar_t szCLSID[39];
  int r= StringFromGUID2()CLSID_Component,szCLSID,39)
  #ifndef _UNICODE
    char szCLSID_single[39];
    wcstombs(szCLSID_single,szCLSID,39(;
  #endif;

其它一些函数:
   StringFromCSLID
   StringFromIID
   StringFromGUID2
  上述函数使用完后需要调用CoTaskMemFree
   CLSIDFromString
   IIDFromString
接口定义宏:
  为了帮助程序员从C到C++的转换,定义了一些宏使同一接口定义对C和C++程序员都能正常工作。在OBJBASE.H和BASETYPES.H中包含了这些定义
例如
  interface IX:IUnknown
{
  virtual vodi _stdcall Fx()=0;
}
使用宏将如下定义
DECLARE_INTERFACE(IX,IUnknown)
{
   STDMETHOD(QueryInterface)(THIS_REFIID,PPVOID) PURE;
   STDMETHOD(ULONG,AddRef)(THIS)PURE;
   STDMETHOD(ULONG,Release)(THIS) PURE;
   STDMETHOD(void,Fx)(THIS) PURE;
}

类厂
  CoCreateInstance创建组件最常用的方法。但缺乏灵活性
  HRESULT _stdcall CoCreateInstance(
    const CLSID &clsid,   //input
    IUnknown *pIUnknown,   //input
    DWORD dwClsContext,   //input
    const IID &iid,      //input
    vodi **ppv           //output
   );
   参数一:组件CLSID
   参数二:用于聚合组件
   参数三:组件执行套间
   参数四:接口IID
   参数五:接受接口指针
参数三可取用的值
  CLSCTX_INPROC_SERVER
  CLSCTX_INPROC_HANDLER
  CLSCTX_LOCAL_SERVER
  CLSCTX_REMOTE_SERVER
在OBJBASE.H中定义了上述值得一些组合常量
  CLSCTX_INPROC:
           CLSCTX_INPROC_SERVER CLSCTX_INPROC_HANDLER
  CLSCTX_ALL
          CLSCTX_INPROC_SERVER  CLSCTX_INPROC_HANDLER
          CLSCTX_LOCAL_SERVER   CLSCTX_REMOTE_SERVER
  CLSCTX_SERVER
         CLSCTX_INPROC_SERVER   CLSCTX_LOCAL_SERVER
         CLSCTX_REMOTE_SERVER
  注意:CLSCTX_REMOTE_SERVER只有把_WIN#@_WINNT>=0x0400才会加入CLSCTX_ALL中和CLSCTX_SERVER中
  CoCreateInstance无法控制组件的创建过程。如果想创建多个实例,更是
  无能为力。

类厂:
   专门用于创建组件的组件。创建类厂的函数:
   HRESULT _stdcall CoGetClassObject(
    const CLSID &clsid,
    DWORD dwClsContect,
    COSERVERINFO *pServerInfo,
    const IID &iid,
    void **ppv
   );
 各参数与CoCreateInstanc基本相同,不过此函数返回的接口指针为该组件
类厂的.指向一个IClassFactory接口.
 interface IClassFactory:IUnknown
  {
   HRESULT _stdcall CreateInstance(IUnknown *pUnknownOuter,
                                  const IID &iid,
                                  void **ppv);
  HRESULT _stdcall LockServer(BOOL bLock);

  }
 
  可以看到其成员函数不接受CLSID参数,也就是它只能创建同传递给CoGetClassObject的CLSID相应的组件。

IClssFactory:
  此接口在IClassFactory的基础上增加了权限功能。

  大多数情况应该使用CoCreateInstanc。
  在以下两种情况应该使用CoGetClassObject:
   1.使用IClassFactory2来创建组件
   2.需创建同一组件的多个实例,使用CoGetClassObject可获得更高的效率.
     因为此时只需要创建一次类厂.

  类厂将是由组件开发人员提供实现.多数情况,类厂与组件在同一个Dll中。
 
  CoGetClassObject通过调用发布组件的Dll中的一个函数DllGetClassObject
来完成类厂的创建。
  STDAPI DllGetClassObject(
      const CLSID &clsid,
      const IID& iid,
      void **ppv
   )
  DllGetClassObject能接受组件的CLSID使得一个DLL可以支持任意数目的组件

 
包容和聚合:
   COM实现继承的方式。是一个组件重用另外一个组件的技术。
   包容:外部组件包含一个指向内部组件接口的指针,外部组件是内部组件的真实客户(外部组件自己可以请求的内部组件的任何接口)。外部组件可以重新实现此接口或将客户的调用转发给内部接口
   聚合:外部组件直接将所含有的聚合对象的指针传递给客户,此时外部组件无需重新实现聚合对象的函数,但是外部组件也无法对聚合对象的任何函数进行改造。对于客户(使用外部组件的)来说不应该知道它在同两个不同的组件交互,否则就是破坏了封装性。能够实现聚合是由于使用了QueryInterface的结果。
   聚合的目标是当客户调用聚合对象的接口函数时,使客户确信它是在同外部接口打交道,即客户能使用的和他所看到应该一样.内部组件对客户而言应该是透明的.即当客户获得内部组件的接口指针时,他所看到的组件功能视图应该是不变的

   (例一:它通过此指针调用QueryInterface查找IUnknown接口时与使用外部组件    调用QueryInterface查找IUnknown接口返回的应该是同一个接口.此时因该把    对内部组件的IUnknown接口查询转发给外部组件的IUnknown接口查询)
   (例二:组件A实现了接口IX,聚合了组件B的IY接口,对客户而言,IX和IY都应该看作是A实现的接口,对这两个接口的调用不因该有区别。如果组件B实现了IY和IZ接口,客户不应该查询到IZ接口。当然组件B不是聚合对象是当然应该能
查到接口IZ。此时通过IX ix->QueryInterface应该可以查到IY,而不应查到
IZ,通过IY iy-〉应该能查到IX,也不应该查到IZ,)
   上述问题的根源在于客户可以获得两个不同的IUnknown接口,所以此时对聚合组件的IUnknow需要经过特殊处理。
   此时内部组件的接口必须使用外部组件实现的IUnknown接口,外部组件的IUnknown接口被称作是外部IUnknown接口或控制IUnknown接口。
  解决的最简单的方法是将内部组件IUnknown的查询转发给外部IUnknown接口查询,为此聚合对象需要一个指向外部IUnknown接口的指针,并且还要知道他是被聚合的(因为当没有聚合时,根本不必转发调用)
   HRESULT _stdcall CoCreateInstance(
           const CLSID &clsid,
           IUnknown *pUnknownOuter,  //此指针指向外部组件的IUnknown
           const IID &iid,
           void **ppv
  );
 上面当pUnknownOuter指针非空时,表示该组件是聚合对象.
 聚合对象实际上要实现两个IUnknown接口,正常情况的IUnknow称作非代理
IUnknown接口,聚合时的称作代理IUnknown接口。
  1.客户--〉聚合对象接口--〉代理IUnknown--〉外部Iunknown
 
  2.外部组件-->代理IUnknown--〉内部组件(外部组件通过非代理IUnknown接口控制内部组件的
    生命期,即调用非代理IUnknown接口的AddRef(),Release())

  实际上非代理IUnknown基本上IUnknown接口的实现一样。代理IUnknown接口实现两个任务,一个是在聚合时将调用转发给
  外部(IUnknown),一个是未聚合时,将调用转发给内部(非代理IUnknown)。
  因此外部组件在创建外部组件时必须请求非代理IUnknown接口,并把它保存下来。否则以后将无法获得此接口,无法获取

  客户创建外部组件时,外部组件的类厂将创建内部组件,并请求聚合的接口,请求成功时,将调用外部组件的AddRef();
  
   //下面的例子CB聚合了CA的IY接口
   //CB::m_pInnerUnknown   -->CA
   //CB::m_pIY             -->CA::IY
   //CA::m_pOuterUnknown   -->CB
 
    
                                                        
 
    //此函数内部创建CA,并请求CA的IUnknown接口 
   1 pB->Initialzie(pOutUnknown)          //pOUtUnknown   -->CB                                          

       CoCreateInstance(CLSID_CA,pOutUnknown,CLSCTX_INPROC_SERVER, IID_IUNKNOWN,&m_pInnerUnknown);
 
     //请求CA的类厂pFactoryA在此过程中把CB的IUnknown传递给CA
   2      pFactoryA->CreateInstance(pOutUnknown)                               
              
              //此处的内部的引用计数增加,当CB析构减少
   3           pA->NoProxyQueryInterface(IID_Unknown,(void**)&CB::m_pInnerUnknown)
  
   //实际调用CA::NoProxyQueryInterface,CA把引用计数转发给外部,增加了CB的引用计数
   4 CB::m_pInnerUnknown->QueryInterface(IID_IY, (void **)CB::m_pIY);
  
   5 pOutUnknown->Release()  //对应于7,不应该对m_pIY维护一个引用计数
                                   
   6 pB->QueryInterface(iid,ppv) //客户请求的接口
                            
  

CB::~CB()
{
  m_cRef = 1;
  IUnknown *  pOutUnknown = this;
  pOutUnknown->AddRef()  //恢复4处的引用计数,对应5
  m_pIY->Release() //对应4
  if(m_pInnerUnknown != NULL)
  {
    m_pInnerUnknown->Release;
  }
}     
//由上面可见CB被析构时,m_cRef=1,所以说引用计数等于多少并没有什么意义,它的存在只是当等于0时,释放组件。
//当客户使用完CB组件的接口后调用Release,此时引用计数已经等于0,组件被delete,析构函数调用,如果不把m_cRef设为1
//当调用文m_pIY后,会使引用计数变为0,析构函数再次被调用,形成循环。
//4处请求完IY接口后,因为不因该对它维护一个引用计数,但是又不能使用m_pIY->Release(),因为这可能会释放此接口占用的一些资源
//所以应该使用传递给CoCreateInstance的指针pOutUnknown来调用Release();                                        
 

盲目聚合
  当外部组件没有客户请求的接口时,不检查聚合接口,而是

   else
   {
     pInnerUnknown->QueryInterface(iid,ppv);
   }
  这样的聚合有可能出现接口不兼容,有两种方法解决问题
1。元接口
   通常不兼容的接口往往是聚合对象的接口与外部组件的接口功能上有重叠。
   不可能出现冲突的接口叫元接口。
2。匹配对
     提供给内部组件或客户关于外部组件的知识。

为了更好的使用聚合对象,有必要给客户或外部组件提供内部组件的状态信息,
为此可以给内部组件加上一个提供状态的接口。

定制组件:
  COM组件可以通过一个定制接口,实现虚拟回调
  客户                      组件
   调用IX    --------------〉IX
   定制ICustomize<---------组件调用ICustomize,提供给客户订制的机会
  良好的接口一般提供一个对订制接口的缺省实现
  
   客户调用IX------>  O---IX
    ICustomize--O     <----------组件调用ICustomize
    聚合或包容
     CCustomize
                         组件CCustomize
                          ICustomize的缺省实现
 
            
编程工作的简化
   客户端:智能指针
         
   服务端:包装类

进程外服务器
   从一格进程的地址空间向另外一进程传递参数,必须调整.
   为对进程外组建进行调整,可以实现一个名为IMarshal的接口.
 
代理:
 EXE服务器,客户同意个模仿组件的DLL进行通信,此DLL可以为客户完成参数调整以及LPC调用.称之为代理
残根:
  也是一个DLL,对客户传来的数据进行反调整,对传回给客户的数据进行调整
 
客户----->代理DLL--调整-->LPC--->残根DLL--反调整-->组件

 


  

 

 

 

 

 

 

 

 

调度接口与自动化

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值