(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) 组件与接口以及函数之间的关系入下图
![](https://i-blog.csdnimg.cn/blog_migrate/bd5af2e7caf405655e448dcb268d5b12.png)
第二部分
(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 PRIVATE
build工程即可。
然后我们实现一个测试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 ;
}
输出结果:
![](https://i-blog.csdnimg.cn/blog_migrate/23ab3e8b1b13fe8b036944a6ecff1f4f.png)