COM浅析

news/2024/7/24 5:04:20

    COM是Component object model的缩写。中文名为组件对象模型。 


     传统的软件由单个的二进制文件组成。组件将单个二进制文件分割为多个独立的部分,每个部分都被称为一个组件。采用组件技术后,在需要对程序进行修改和改进时,只需替换某个组件模块即可。

      组件化程序设计思想是将复杂的应用程序被设计成小的、功能单一的组件模块。为了实现这样的软件,模块与模块之间需要一些规范,这些规范规定了模块如何划分以及模块之间的交互的问题。

  

         COM标准就是这样一组规范。


     COM对象是建立在二进制可执行代码级的基础上,因此COM对象是语言无关的。只要它们能生成符合COM规范的可执行代码,在任何平台都可以使用。这一特性使使用不同语言开发的组件进行交互成为可能。COMMicrosoft定义的。在windows系统平台上COM技术被应用于系统的各个层次。

 

     组件技术是面向接口的。接口封装了软件内部的细节,只要留给客户的接口没有改变,客户端代码就不需要重新编译。软件工程一直强调软件模块的强内聚,低耦合。COM技术很好的遵循这种原则因而被广泛的使用。

     在COM中,一个组件程序被称为一个模块。COM组件是以win32动态链接库dll或可执行文件的形式发布可执行代码。一个组件中可以有多个COM对象。


     组件可以是一个dll,此时的组件为进程内组件。

     组件也可以是exe,这时的组件是进程外组件。


      由于COM的封装性,各种实现机制都被封装在了模块内部,客户在使用时仅仅需要得到接口。也就是说客户与组件是靠接口建立联系的。对于一个C++类来说它的接口是它public下的一组成员函数。对于一个dll来说它的接口是它导出的一组函数 。对于COM来说它的接口就是一组内存结构。由于使用C++来实现COM非常的简单,所以很多COM都是使用C++来写的,但是使用其他语言也是可以的,只要符合COM规范。


      C++使用抽象基类来实现COM接口。COM可能有很多个接口,因此我们可以采用多重继承,从抽象基类中继承来实现多个接口。但是接口也并不一定需要继承得到,COM标准并没有要求实现某个接口的类要从某个抽象基类继承。对接口的继承仅仅是一种实现细节而已。因此,除了使用不同的抽象基类通过多重继承实现多个接口外,还可以在一个类中直接实现多个接口。

 

     客户只能通过接口同组件打交道,对客户来说组件就是一组接口集。 客户它并不知道某个组件内部提供了哪些接口。事实上客户对组件知道的越少,在不影响客户运行的情况下组件就可以最大限度的发生变化。

 

 

    接口查询

 

    每一个COM接口都必须从IUnknown接口中继承而来。因为IUnknown接口提供了两个重要的特性:生存期控制和接口查询。

客户对于组件有哪些接口并不清楚,但是客户可以通过调用每个组件都有的IUnknown接口中的QueryInterface函数来查询组件中的接口。   

 

IUnknown接口的实现。
class IUnknown
{
  public:
     virtual HRESULT _stdcall QueryInterface(const IID&id,void **pv)=0;
      virtual ULONG _stdcall AddRef()=0;
      virtual ULONG _stdcall Release()=0;
};


 

     QueryInterface用于查询COM对象其他接口指针。AddRefRelease用于增加和减少引用计数。

     由于所有的接口都从IUnknown继承而来因此每个接口的虚函数表的前三项都是QueryInterfaceAddRefRelease。这使得所有的COM接口都被当作IUnknown接口来处理。这就是所谓的多态性。所有的接口也都支持QueryInterface。因此所有的COM接口都可以被客户用来查询它所支持的接口。若支持则返回该接口的指针,否则返回一个错误代码。

HRESULT _stdcall QueryInterface(const IID&id,void **pv);

    其中IID是一个数据结构,用于标识每个接口,ppv返回接口指针。返回值HRESULT是一个32位值,标识函数执行成功或失败。但返回值不能直接同S_OKS_FAIL进行比较,而应该使用SUCCEEDEDFAILED宏进行判断。因为成功或失败的值或有多个,采用上面的宏会对返回值的最高两位进行判断,看是否成功或失败。


IX的thisCAthis指针位置相同。因此this==static_cast<IX*>(this); IY的this指针在CA中位置的不同,将this转换为IY*需要添加一个偏移量this+Δy==static_cast<IY*>(this);

     由于CA多重继承与IXIY,IXIY又继承于IUnknown,因此将this转换为IUnknown*具有二义性:

    *ppv=static_cast<IUnknown*>(this);

    此时*ppv的值是static_cast<IUnknown*>(static_cast<IX*>(this);

static_cast<IUnknown*>(static_cast<IY*>(this));

     在id==IID_IUnknown时,*ppv=static_cast<IX*>(this)

其实这时无关紧要的,*ppv=static_cast<IY*>(this)也是可以的。但是要保持一致。

 

    接下来将会介绍动态链接库DLL对于组件构造软件系统的重要性。


    我们会把组件放在DLL中。但是必须要明白的是DLL并不是组件,组件也并不是DLL。DLL只是实现组件的一种方式。如果你对DLL还不是很明白,可以参考我的博文:Windows核心编程系列谈谈dll。
前面代码中客户调用CreateInstance获得组件的一个实例并得到一个IUnknown接口指针。在使用dll实现组件时也有类似的函数。且此函数需要从dll中导出。

   

external “C” __declspec(dllexport) IUnknown *CreateInstance()
{
   IUnknown*pI=static_cast<IX*>(new CA);
   pI->AddRef();
   return pI;
}


    在客户代码中,与组件共享的文件包括dll的头文件dll.h以及各接口的声明的头文件。GUID.h中存储的是各接口的IID,也提供给了客户代码。


     HRESULT经常被用作返回值。向其用户报告各种情况。如QueryInterface返回的就是一个HRESULT值。HRESULT实际上是一个32位的值。通常被定义为DWORD或long类型。系统返回的HRESULT的值在Win32的WINERROR.h有定义。HRESULT与win32错误代码有些类似,但是并不完全一样。HRESULT可分为三个部分。但是最重要的是最高的两位,它反映了函数调用结果的基本情况。如成功、失败、警告、错误。


    因为成功或失败的情况并不止一个。比如函数调用失败,失败也包括很多具体的情况。后面的位数表示为什么失败。成功也是一样。这也是在调用QueryInterface或其他函数时使用SUCCEEDED或FAILED宏进行判断。因为这两个宏或检查返回值的最高两位检查成功或失败。不能简单的把返回值与S_OK或E_FAIL进行比较。因为函数执行成功后可能有不同的成功返回值,执行失败之后也会有不同的失败返回值。第16-28位表示设备代码。标识了操作结果来自那个设备。现在可以不作考虑。低16位为错误代码。只有当设备代码全为0时HRESULT值才与win32错误代码对应发现HRESULT值不在win32错误代码中,可查找低16位与之相同的win32错误代码。


    前面我们曾说过IID是一个标识接口的常量。实际上IID是一个16字节的GUID结构。GUID是global unique ID全球唯一标识符。IID是接口ID的缩写。在不经过任何机构的协调通过编程方法来生成的GUID是唯一的。VC提供了一个GUIDGEN.exe的工具,每次运行都可以保证生成的GUID全球唯一。GUID具有时间和空间上的唯一。它是根据当前时间和网卡的地址生成的 。没网卡的计算机则使用其他算法生成。GUID结构很大,一般不在程序中到处复制。在传参时一般采用传常量引用的方式。


   GUID不仅可以用于标识接口,还可以标识组件。当标识组件时为CLSID。即class id。同接口一样,每个组件都有一个不同的CLSID。
   

#include<iostream> 
using namespace  std;
 

#ifdef __cpp
 #define interface class
#else
 #define interface struct
#endif
 

void trace(const char * msg) {cout << msg << endl ;}

typedef _Return_type_success_(return >= 0) long HRESULT;

#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0)

typedef unsigned long       ULONG;

#define E_NOINTERFACE                 ((HRESULT)0x80004002L)
#define S_OK                                   ((HRESULT)0L)

typedef struct _GUID {
    unsigned long  Data1;
    unsigned short Data2;
    unsigned short Data3;
    unsigned char  Data4[ 8 ];
	 
} GUID;

	bool operator  ==( const GUID  & iid1   ,const GUID  & iid2 )
	{
		int  ret= memcmp( &iid1,  &iid2, sizeof(GUID)  );
		return    ret == 0 ;
	}


typedef GUID IID;


//IIDs 
static const IID  IID_IUnknown=     {0, 0, 0,   {0, 0, 0, 0, 0, 0, 0, 0}}; 
//{32bb8320-b41b-11cf-a6bb-0080c7b2d682} 
static const IID  IID_IX =   {0x32bb8320, 0xb41b, 0x11cf,   {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};

//{32bb8321-b41b-11cf-a6bb-0080c7b2d682} 
static const IID  IID_IY =  {0x32bb8321, 0xb41b, 0x11cf,    {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};

//{32bb8322-b41b-11cf-a6bb-0080c7b2d682}
static const IID  IID_IZ = {0x32bb8322, 0xb41b, 0x11cf,  {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};



interface  IUnKnown
{
virtual HRESULT __stdcall QueryInterface(const IID& iid,void **ppv)=0;
virtual ULONG __stdcall AddRef()=0;
virtual ULONG __stdcall Release()=0;
};
 

interface IX : IUnKnown 
{
	    virtual void __stdcall Fx() = 0; 
};

interface IY : IUnKnown
{ 
	  virtual void __stdcall Fy() = 0; 
};

interface IZ : IUnKnown
{
	  virtual void __stdcall Fz() = 0; 
};

 
/*
//Forward references for GUIDs 
extern const IID IID_IX; 
extern const IID IID_IY; 
extern const IID IID_IZ;
 */

class CA : public IX, public IY
{ 
         virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv); 
         virtual ULONG  __stdcall AddRef()
		 {
			 return 0;
		 } 
         virtual ULONG  __stdcall Release() 
		 {
			 return 0;
		 }
         virtual void       __stdcall Fx() 
		 {
			 cout << "CA::Fx" << endl ;
		 }    
         virtual void       __stdcall Fy()
		 {
			 cout << "CA::Fy" << endl ;
		 } 
};

 

HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv)
{ 
         if(iid ==  IID_IUnknown) 
         { 
               trace("返回IUnknown接口!");
				    /*
				      由于CA多重继承与IX和IY,而IX,IY又继承于IUnknown.
                      因此将this转换为IUnknown*具有二义性:  *ppv=static_cast<IUnknown*>(this); 
				   */
               *ppv = static_cast< IX* >(this);   
         }
		 else if(iid == IID_IX) 
         {
			     trace("返回IX接口!"); 
                 *ppv = static_cast< IX* >(this); 

         } 
         else if(iid == IID_IY) 
         { 
                  trace("返回IY接口!");
                  *ppv = static_cast< IY* >(this); 
         }
         else 
         {
			    trace("接口不支持!"); 
                *ppv = NULL; 
                return E_NOINTERFACE; 
         }

		  printf("%p\n", *ppv);

         reinterpret_cast< IUnKnown* >(*ppv)->AddRef(); 
         return S_OK; 
}

 
IUnKnown* CreateInstance() 
{
         IUnKnown* pI = static_cast< IX* >(new CA); 
         pI->AddRef(); 
         return pI; 
}
 
 
int main()
{

         HRESULT hr; 
         trace("客户端得到一个未知的IUnKnown接口!"); 
         IUnKnown* pIUnknown = CreateInstance();

 
		 trace("\r\n");


         trace("客户端查询IX接口!"); 
         IX* pIX = NULL; 
         hr = pIUnknown->QueryInterface(IID_IX,(void**)&pIX); 
         if(SUCCEEDED(hr)) 
         { 
                   trace("获取 IX 接口成功!");
                    pIX->Fx();    
         }

       trace("\r\n");
 
		 trace("客户端查询IY接口!"); 
         IY* pIY = NULL; 
         hr = pIUnknown->QueryInterface(IID_IY,(void**)&pIY); 
         if(SUCCEEDED(hr)) 
         { 
                    trace("获取 IY  接口成功!");
                   pIY->Fy();      
         }

         trace("\r\n");           
 
	     trace("客户端查询IZ接口!"); 
         IZ* pIZ = NULL; 
         hr = pIUnknown->QueryInterface(IID_IZ,(void**)&pIZ); 
         if(SUCCEEDED(hr)) 
         {  
              pIZ->Fz(); 
         } 
         else 
         { 
               trace("获取 IZ接口失败!");
         }


	     trace("\r\n");     
 
         trace("从IX查询IY接口!"); 
         IY* pIYfromIX = NULL; 
         hr = pIX->QueryInterface(IID_IY, (void**)&pIYfromIX); 
         if(SUCCEEDED(hr)) 
         {
			     trace("获取 IY接口成功!"); 
                 pIYfromIX->Fy(); 
         }

		  trace("\r\n");     


		 trace("从IY查询IUnKnown接口!"); 
         IUnKnown* pIUknownFromIY = NULL; 
         hr = pIY->QueryInterface(IID_IUnknown, (void**)&pIUknownFromIY); 
         if(SUCCEEDED(hr)) 
         {  
                trace("获取IUnKnown接口成功!"); 
         }
 
     
		  //删除
         delete pIUnknown;

         return 0;

}




http://www.niftyadmin.cn/n/1300863.html

相关文章

axios携带cookie配置详解(axios+koa)

话不多说&#xff0c;一个字&#xff0c;干&#xff01; 前端配置如下&#xff1a; axios.defaults.withCredentials true; //配置为trueaxios.post(http://localhost:3000/tpzdz/vote/all, {openid: oJ0mVw4QrfS603gFa_uAFDADH2Uc,date: 2018-11-21 }).then(function (resp…

DLL 几种类型

1、Non-MFCDLL(非MFC动态库) 这种动态链接库指的是不用MFC的类库结构,直接用C语言写的DLL,其导出的函数是标准的C接口,能被非MFC或MFC编写的应用程序所调用。 如果建立的DLL不需要使用MFC,那么应该建立Non-MFCDLL,因为使用MFC会增大用户库的大小,从而浪费用户的磁盘和…

vue中eslintrc.js配置最详细介绍

本文是对vue项目中自带文件eslintrc.js的内容解析&#xff0c; 介绍了各个eslint配置项的作用&#xff0c;以及为什么这样设置。 比较详细&#xff0c;看完能对eslint有较为全面的了解&#xff0c;基本解除对该文件的疑惑。 /*** 参考文档* 【eslint英文文档】https://eslint.…

模板实现多态

熟悉模板编程的朋友或许听到过这个技巧或者模式&#xff1a;Barton-Nackmann 技巧或者称 奇异循环模板模式&#xff08;Curiously Recurring Template Prattern&#xff09;。 其实在 《c 编程语言》这本bible 书里&#xff0c;在模板那章提到过一个很奇妙的类的实现&…

阿里P7谈:前端工程师的技术进阶点在哪里?

前端开发的难点到底在什么地方&#xff1f; 这个问题是一个比较全能的JavaP7工程师提出来的,总结一下大家的回答&#xff1a; 需求变化快,需要良好的复用、可拓展能力&#xff0c;否则动不动重写。 兼容性问题,需要兼容各种移动设备的各种浏览器。 CSS非正交,对于绝大多数人来…

select加线程的服务端代码

服务端: #include"stdafx.h" #include <WinSock2.h> #include <iostream> using namespace std; #pragma comment(lib,"Ws2_32.lib") //存储所有的客户端套接字 fd_set g_fdClientSock; //线程函数声明 DWORD WINAPI Threa…

30分钟学会Git-黄强-专题视频课程

30分钟学会Git—599人已学习 课程介绍 Git 是目前世界上先进、流行、优秀、用户多的免费开源的分布式版本控制系统 &#xff0c;全世界的大牛都在用&#xff0c;各大代码托管平台都在用&#xff0c;国内外知名IT公司都在用&#xff0c;如果作为一个项目开发者&#xff0c;你…

webpack 静态资源集中输出的方法示例

目录结构 copy-webpack-plugin 工作中会有一些已经存在但在项目中没有引用的图片资源或者其他静态资源&#xff08;比如设计图、开发文档&#xff09;&#xff0c;这些静态资源有可能是文档&#xff0c;也有可能是一些额外的图片。打包时保留这些静态资源&#xff0c;直接打包…