COM是Component object model的缩写。中文名为组件对象模型。
传统的软件由单个的二进制文件组成。组件将单个二进制文件分割为多个独立的部分,每个部分都被称为一个组件。采用组件技术后,在需要对程序进行修改和改进时,只需替换某个组件模块即可。
组件化程序设计思想是将复杂的应用程序被设计成小的、功能单一的组件模块。为了实现这样的软件,模块与模块之间需要一些规范,这些规范规定了模块如何划分以及模块之间的交互的问题。
COM标准就是这样一组规范。
COM对象是建立在二进制可执行代码级的基础上,因此COM对象是语言无关的。只要它们能生成符合COM规范的可执行代码,在任何平台都可以使用。这一特性使使用不同语言开发的组件进行交互成为可能。COM是Microsoft定义的。在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对象其他接口指针。AddRef和Release用于增加和减少引用计数。
由于所有的接口都从IUnknown继承而来因此每个接口的虚函数表的前三项都是QueryInterface,AddRef,Release。这使得所有的COM接口都被当作IUnknown接口来处理。这就是所谓的多态性。所有的接口也都支持QueryInterface。因此所有的COM接口都可以被客户用来查询它所支持的接口。若支持则返回该接口的指针,否则返回一个错误代码。
HRESULT _stdcall QueryInterface(const IID&id,void **pv);
其中IID是一个数据结构,用于标识每个接口,ppv返回接口指针。返回值HRESULT是一个32位值,标识函数执行成功或失败。但返回值不能直接同S_OK或S_FAIL进行比较,而应该使用SUCCEEDED或FAILED宏进行判断。因为成功或失败的值或有多个,采用上面的宏会对返回值的最高两位进行判断,看是否成功或失败。
IX的this与CA的this指针位置相同。因此this==static_cast<IX*>(this); 但IY的this指针在CA中位置的不同,将this转换为IY*需要添加一个偏移量this+Δy==static_cast<IY*>(this);
由于CA多重继承与IX和IY,而IX,IY又继承于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;
}