(1)Com组件是?
Com组件是以Win32动态链接库(DLL)或可执行文件(EXEs)的形式发布的可执行代码组成的。
(2)接口概念
DLL的接口就是它所输出的那些函数;C++类的接口则是该类的一个成员函数集。对于Com来说,接口是一个包含一个函数指针数组的内存结构。每一个数组元素包含的是一个由组件所实现的函数的地址。对于Com而言,接口就此内存结构。
(3)接口的作用
在Com中接口就是一切。对于客户来说,一个组件就是一个接口集。客户只能通过接口才能同com组件打交道。
用类C++的方法来实现组件
(1)接口定义
#define interface struct //接口 interface IX { virtual void Fx1() = 0; //纯虚函数 virtual void Fx2() = 0; }; interface IY { virtual void Fy1() = 0; virtual void Fy2() = 0; }; //"组件" class CA : public IX, public IY { public: virtual void Fx1() { cout << "Fx1" << endl; } virtual void Fx2() { cout << "Fx2" << endl; } virtual void Fy1() { cout << "Fy1" << endl; } virtual void Fy2() { cout << "Fy2" << endl; } }(2) 客户调用
void trace(const char* pMsg) { cout << pMsg << endl; } int main() { trace("Client: Create an instance of the component"); //创建一个组件 CA* pA = new CA; //得到一个接口 IX* pIX = pA; pIX->Fx1(); pIX->Fx2(); //使用方法 IY* pIY = pA; pIY->Fy1(); pIY->Fy2(); delete pA; return 0; }通过以上实例,可以掌握:
(1)Com接口在C++中是用纯抽象基类实现的。
(2)一个Com组件可以提供多个接口。
(3)一个c++类可以使用多继承来实现一个可以提供多个接口的组件。
(4) 组件与接口以及函数之间的关系入下图
第二部分
(1)客户同组件的交互都是通过一个接口完成的。在客户查询组件的其它接口时,也是通过接口完成的。这个接口就是IUnknown。它定义在UNKNWN.H头文件中。
interface IUnknown {d virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv) = 0; virtual ULONG __stdcall AddRef() = 0; virtual ULONG __stdcall Release() = 0; };所有的Com接口都继承了IUnknown,每个接口的vtbl中的前三个函数都是QueryInterface,AddRef,Release。这就保证了所有的接口都可以被当成IUnknown接口来处理。
(2)IUnknown指针的获取
IUnkown* CreateInstance();这个并不是最终com选择的方式。客户可以通过该函数来创建组件而不用再使用new操作符。有了IUnkown指针,我们就可以调用QueryInterface来调用其它接口函数了。
(3)QueryInterface函数
HRESULT __stdcall QueryInterface(const IID& iid,void** ppv);其中第一个参数标识客户所需的接口,现在可以理解为是一个常量。另一个指针是QueryInterface存放所请求接口指针的地址。
编写该函数必须遵守的规则:
v1: QueryInterface返回的总是同一IUnknown指针。
v2: 若客户曾经获取过某个接口,那么它将总能获取此接口。
v3: 客户可以再次获取已经拥有的接口。
v4:客户可以返回到起始接口。
v5:若能够从某个接口获取某特定接口,那么可以从任意接口都将可以获取此接口。
HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv) { if( iid == IID_IUnknown ) { *ppv = static_cast<IX*>(this); //均返回默认接口 } else if( iid == IID_IX ) { *ppv = static_cast<IX*>(this); } else if( iid == IID_IY ) { *ppv = static_cast<IY*>(this); } else { *ppv = NULL; return E_NOINTERFACE; } static_cast<IUnknown*>(*ppv)->AddRef(); //引用计数加1 }
我们说接口不会发生变化到底是什么含义呢?每一个接口都有一个唯一的接口标识符(IID)。一般情况下,我们不会改变接口,而可以建立一个新的接口并为之指定一个新的IID。当QueryInterface接收到对老IID的查询时,它将返回老的接口。而当它收到对新的IID的查询时,它将返回新的接口。所以同某个IID相应的接口将绝对不会发生变化。
(4)AddRef 和 Release
实现的是一种名为引用计数的内存管理技术,用来维护组件的生命周期。当客户从组件取得一个接口时,此引用计数将增1.当客户使用完某个接口后,组件的引用计数值减1.当引用计数为0时,组件将自己从内存中删除。当创建某个已有接口的另外一个引用时,客户也将会增大相应的组件的引用计数值。
为正确的使用引用计数,需要了解以下三条简单的规则:
v1: 在返回之前调用AddRef。对于那些返回接口指针的函数,在返回之前应用相应的指针调用AddRef。这些函数包括QueryInterface和CreateInstance。 这样,客户通过这种函数得到一个接口后,它将无需调用AddRef。
v2:使用完接口之后调用Release。在使用完某个接口之后应调用此接口的Release函数。
v3: 在赋值之后调用AddRef。
具体的规则定义如下:
V1: 输出参数规则
输出参数指的是给函数的调用者传回一个值的函数参数。在函数体中将设置此输出参数的值而不会使用调用者传进来的值。相当于输出参数为返回值。那么当这个参数为 一个接口的时候,就必须调用AddRef。
V2: 输入参数规则
输入参数指的是给函数传递某个值的参数。在函数体中将会使用这个值但是不会修改它或者将其返回给调用者。那么无需做任何操作,相当于值传递。
V3:输入-输出规则
表示一个参数同时具有输入和输出功能。那么必须在给它赋予另一个接口指针值之前调用其Release。
V4:局部变量规则
局部变量无需调用AddRef和Release。
V5:不能确定时规则
对于任何不能确定的情形,都应调用AddRef和Release对。
V6:全局变量规则
对于保存在全局变量中的接口指针,在将其传递给另外一个函数之前,必须调用其AddRef。同理,对于类成员变量的接口指针也一样。
第三部分:
(1)动态链接库
将组建放入动态链接库中,这并不是我们要将一个组件变成一个DLL。一个组件实际上并不是一个DLL, DLL只是一个组件服务器,或者说是一种发行组件的方式。 组件实际上应看成是在DLL中所实现的接口集。DLL只是一种形式。
我们现在首先来编写利用DLL来实现的Com组件。
v1: 新建一个CMPNT2的Dll工程
v2: 定义IFace.h接口文件
// // Iface.h // // Interfaces 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 "C" { extern const IID IID_IX ; extern const IID IID_IY ; extern const IID IID_IZ ; }v3: 定义GUID.cpp的IID申明文件
// // GUIDs.cpp - Interface IDs // #include "stdafx.h" #include <objbase.h> extern "C" { // {32bb8320-b41b-11cf-a6bb-0080c7b2d682} extern const IID IID_IX = {0x32bb8320, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ; // {32bb8321-b41b-11cf-a6bb-0080c7b2d682} extern const IID IID_IY = {0x32bb8321, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ; // {32bb8322-b41b-11cf-a6bb-0080c7b2d682} extern const IID IID_IZ = {0x32bb8322, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ; // The extern is required to allocate memory for C++ constants. }v4: 定义接口的实现文件,即组件。并在此文件中实现输出函数。
CMPNT2.cpp
// CMPNT2.cpp : Defines the exported functions for the DLL application. // #include "stdafx.h" // // Cmpnt2.cpp // To compile, use: cl /LD Cmpnt2.cpp GUIDs.cpp UUID.lib Cmpnt2.def // #include <iostream> #include <objbase.h> #include "Iface.h" using namespace std; void trace(const char* msg) { cout << "Component 2:\t" << msg << endl ;} //extern "C" _declspec(dllexport) IUnknown* CreateInstance(); // // Component // class CA : public IX, public IY { // IUnknown implementation virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) ; virtual ULONG __stdcall AddRef() ; virtual ULONG __stdcall Release() ; // Interface IX implementation virtual void __stdcall Fx() { cout << "Fx wll" << endl ;} // Interface IY implementation virtual void __stdcall Fy() { cout << "Fy wll" << endl ;} public: // Constructor CA() : m_cRef(0) {} // Destructor ~CA() { trace("Destroy self.") ;} private: long m_cRef ; } ; HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv) { if (iid == IID_IUnknown) { trace("Return pointer to IUnknown.") ; *ppv = static_cast<IX*>(this) ; } else if (iid == IID_IX) { trace("Return pointer to IX.") ; *ppv = static_cast<IX*>(this) ; } else if (iid == IID_IY) { trace("Return pointer to IY.") ; *ppv = static_cast<IY*>(this) ; } else { trace("Interface not supported."); *ppv = NULL ; return E_NOINTERFACE ; } reinterpret_cast<IUnknown*>(*ppv)->AddRef() ; return S_OK ; } ULONG __stdcall CA::AddRef() { return InterlockedIncrement(&m_cRef) ; } ULONG __stdcall CA::Release() { if (InterlockedDecrement(&m_cRef) == 0) { delete this ; return 0 ; } return m_cRef ; } // // Creation function // extern "C" IUnknown* CreateInstance() { IUnknown* pI = static_cast<IX*>(new CA) ; pI->AddRef() ; return pI ; }v5: 定义导出函数文件:
CMPNT2.def
; ; Cmpnt1 module-definition file ; LIBRARY Cmpnt1.dll DESCRIPTION '(c)1996-1997 Dale E. Rogerson' EXPORTS CreateInstance @1 PRIVATEbuild工程即可。
然后我们实现一个测试Dll组件的工程TestDllCom。
v1. 将guid.cpp 和 iface.h两个文件拷贝到该工程文件中。
v2. 创建create.h 和 create.cpp文件用于加载dll并获取导出函数。
// // Create.h // IUnknown* CallCreateInstance(char* name) ;
// // Create.cpp // #include <iostream> #include <unknwn.h> // Declare IUnknown. #include "Create.h" using namespace std; typedef IUnknown* (*CREATEFUNCPTR)() ; IUnknown* CallCreateInstance(char* name) { // Load dynamic link library into process. HINSTANCE hComponent = ::LoadLibrary(name) ; if (hComponent == NULL) { cout << "CallCreateInstance:\tError: Cannot load component." << endl ; return NULL ; } // Get address for CreateInstance function. CREATEFUNCPTR CreateInstance = (CREATEFUNCPTR)::GetProcAddress(hComponent, "CreateInstance") ; if (CreateInstance == NULL) { cout << "CallCreateInstance:\tError: " << "Cannot find CreateInstance function." << endl ; return NULL ; } return CreateInstance() ; }v3: 主要的测试Main函数
// // Client2.cpp // To compile, use: cl Client2.cpp Create.cpp GUIDs.cpp UUID.lib // #include <iostream> #include <objbase.h> #include "Iface.h" #include "Create.h" using namespace std; void trace(const char* msg) { cout << "Client 2:\t" << msg << endl ;} // // Client // int main() { HRESULT hr ; // Get the name of the component to use. char name[40] ; cout << "Enter the filename of a component to use [Cmpnt?.dll]: " ; cin >> name ; cout << endl ; // Create component by calling the CreateInstance function in the DLL. trace("Get an IUnknown pointer.") ; IUnknown* pIUnknown = CallCreateInstance(name) ; if (pIUnknown == NULL) { trace("CallCreateInstance Failed.") ; return 1 ; } trace("Get interface IX.") ; IX* pIX ; hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX) ; if (SUCCEEDED(hr)) { trace("Succeeded getting IX.") ; pIX->Fx() ; // Use interface IX. pIX->Release() ; } else { trace("Could not get interface IX.") ; } trace("Ask for interface IY.") ; IY* pIY ; hr = pIUnknown->QueryInterface(IID_IY, (void**)&pIY) ; if (SUCCEEDED(hr)) { trace("Succeeded getting IY.") ; pIY->Fy() ; // Use interface IY. pIY->Release() ; } else { trace("Could not get interface IY.") ; } trace("Release IUnknown interface.") ; pIUnknown->Release() ; return 0 ; }输出结果: