目录
一. COM是一个更好的 C++ 1. COM 是什么 2. 从 C++ 到 DLL 再到 COM 2.1 C++ 2.2 DLL 2.3 COM 二. COM基础 1. COM基本知识 1.1 返回值HRESULT 1.2 初识idl 1.3 IUnkown接口 2. 一个比较简单的COM 2.1 interface.h文件 2.2 math.h文件 2.3 math.cpp文件 2.4 simple.cpp文件 2.5 Math组件的二进制结构图 2.6 小结 三. 纯手工创建一个COM组件 1. 从建工程到实现注册 1.1 创建一个类型为win32 dll工程 1.2 定义接口文件 1.3 增加注册功能 1.3.1 增加一个MathCOM.def文件 1.3.2 DllRegisterServer()和DllUnregisterServer() 1.4 MathCOM.cpp文件 1.5 小结 2. 实现ISmipleMath,IAdvancedMath接口和DllGetClassObject() 2.1 实现ISmipleMath和IAdvancedMath接口 2.2 COM组件调入大致过程 2.3 DllGetClassObject()实现 2.4 客户端 2.5 小结 3. 类厂 附录 A 我对dll的一点认识 一. 没有lib的dll 1.1 建一个没有lib的dll 1.2 调试没有lib的dll 二. 带有lib的dll 2.1 创建一个带有lib的dll 2.2 调试带有引用但没有头文件的dll 三. 带有头文件的dll 3.1 创建一个带有引出信息头文件的dll 3.2 调试带有头文件的dll 四. 小结 |
1、COM 是什么
Don Box 说"COM IS LOVE"。COM 的全称是 Component Object Model 组件对象模型。
2、从 C++ 到 DLL 再到 COM
2.1 C++
如某一软件厂商发布一个类库(CMath四则运算),此时类库的可执行代码将成为客户应用中不可分割的一部分。假设此类库的所产生的机器码在目标可执行文件中占有4MB的空间。当三个应用程序都使用CMath库时,那么每个可执行文件都包含4MB的类库代码(见图1.1)。当三个应用程序共同运行时,他们将会占用12MB的虚拟内存。问题还远不于此。一旦类库厂商发现CMath类库有一个缺陷后,发布一个新的类库,此时需要要求所有运用此类库的应用程序。此外别无他法了。
图1.1 CMath 的三个客户
2.2 DLL
解决上面问题的一个技术是将CMath类做成动态链接库(DLL ,Dynamic Link Library)的形式封装起来 。
在使用这项技术的时候,CMath的所有方法都将被加到 CMath dll 的引出表(export list)中,而且链接器将会产生一个引入库(import library)。这个库暴露了CMath的方法成员的符号 。当客户链接引入库时,有一些存根会被引入到可执行文件中,它在运行时通知装载器动态装载 CMath Dll。
当 CMath 位于dll中时,他的运行模型见图1.2
图1.2 CMath引入库
2.3 COM
"简单地把C++类定义从dll中引出来"这种方案并不能提供合理的二进制组件结构。因为C++类那既是接口也是实现。这里需要把接口从实现中分离出来才能提供二进制组件结构。此时需要有二个C++类,一个作为接口类另一个作为实现类。让我们开始COM之旅吧。
二、COM基础
1、 COM基本知识
1.1 返回值HRESULT
COM要求所有的方法都会返回一个HRESULT类型的错误号。HRESULT 其实就一个类型定义:
1.
typedef
LONG
HRESULT
;
有关HRESULT的定义见 winerror.h 文件
01.
// Values are 32 bit values layed out as follows:
02.
//
03.
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
04.
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
05.
// +-+----+-------------------------+---------------------------------+
06.
// |S| Res| Facility | Code |
07.
// +-+----+-------------------------+---------------------------------+
08.
//
09.
// where
10.
//
11.
// S - is the severity code
12.
//
13.
// 0 - Success
14.
// 1 - Error
15.
//
16.
// Res- is a reserved bit
17.
//
18.
// Facility - is the facility code
19.
//
20.
// Code - is the facility''s status code
我们一般下面的宏来判断方法是否成功:
1.
#define SUCCEEDED(hr)(long(hr)>=0)
2.
#define FAILED(hr)(long(hr)<0)
1.2 初识 IDL
每个标准的COM组件都需要一个接口定义文件,文件的扩展名为IDL。让我们看IUnknow接口的定义文件是怎样的。
01.
[
02.
local,
03.
object,
04.
uuid
(00000000-0000-0000-C000-000000000046),
05.
pointer_default(unique)
06.
]
07.
08.
interface IUnknown
09.
{
10.
typedef
[unique] IUnknown *LPUNKNOWN;
11.
12.
cpp_quote(
"////////////////////////////////////////////////////"
)
13.
cpp_quote(
"// IID_IUnknown and all other system IIDs are provided in UUID.LIB"
)
14.
cpp_quote(
"// Link that library in with your proxies, clients and servers"
)
15.
cpp_quote(
"////////////////////////////////////////////////////"
)
16.
17.
HRESULT
QueryInterface(
18.
[in] REFIID riid,
19.
[out, iid_is(riid)]
void
**ppvObject);
20.
ULONG
AddRef();
21.
ULONG
Release();
22.
}
23.
24.
[local]属性禁止产生网络代码。
25.
[object]属性是表明定义的是一个COM接口,而不是DEC风格的接口。
26.
[
uuid
]属性给接口一个GUID。
27.
[unique]属性表明null(空)指针为一个合法的参数值。
28.
[pointer_defaul]属性所有的内嵌指针指定一个默认指针属性
29.
typedef
[unique] IUnknown *LPUNKNOWN;这是一个类型定义
30.
cpp_quote这个比较有趣,这是一个在idl文件写注解的方法。这些注解将保存到***.h和***_i.c文件中
31.
[in]表示这个参数是入参
32.
[out]表示这个参数是出参
33.
[iid_is(riid)]表示这个参数需要前一个的riid 参数。
注意:所有具有out属性的参数都需要是指针类型。
1.3 IUnkown接口
在整个例子除了IUnkown这个东西,其他应该不会感到陌生吧!COM要求(最基本的要求)所有的接口都需要从IUnknown接口直接或间接继承,所以IUnknown接口有"万恶之源"之称。
IUnkown接口定义了三个方法。
1.
HRESULT
QueryInterface([in] REFIID riid,[out]
void
**ppv);
2.
ULONG
AddRef();
3.
ULONG
Release();
其中 AddReft() 和Release()负责对象引用计数用的,而 QueryInterface()方法是用于查询所实现接口用的。每当COM组件被引用一次就应调用一次AddRef()方法。而当客户端在释放COM组件的某个接口时就需要调用Release()方法。
这里所讲的请在下面的例子仔细体会。
2、一个比较简单的COM
此例子共有四个文件组成:
文件名 | 说明 |
Interface.h | 接口类定义文件 |
Math.h和Math.cpp | 实现类文件 |
Simple.cpp 主函数文件 | 这里用来当作COM的客户端 |
2.1 interface.h 文件
01.
#ifndef INTERFACE_H
02.
#define INTERFACE_H
03.
#include
04.
05.
//{7C8027EA-A4ED-467c-B17E-1B51CE74AF57}
06.
static
const
GUID IID_ISimpleMath =
07.
{ 0x7c8027ea, 0xa4ed, 0x467c, { 0xb1, 0x7e, 0x1b, 0x51, 0xce, 0x74, 0xaf, 0x57 } };
08.
09.
//{CA3B37EA-E44A-49b8-9729-6E9222CAE84F}
10.
static
const
GUID IID_IAdvancedMath =
11.
{ 0xca3b37ea, 0xe44a, 0x49b8, { 0x97, 0x29, 0x6e, 0x92, 0x22, 0xca, 0xe8, 0x4f } };
12.
13.
interface ISimpleMath :
public
IUnknown
14.
{
15.
public
:
16.
virtual
int
Add(
int
nOp1,
int
nOp2) = 0;
17.
virtual
int
Subtract(
int
nOp1,
int
nOp2) = 0;
18.
virtual
int
Multiply(
int
nOp1,
int
nOp2) = 0;
19.
virtual
int
Divide(
int
nOp1,
int
nOp2) = 0;
20.
};
21.
22.
interface IAdvancedMath :
public
IUnknown
23.
{
24.
public
:
25.
virtual
int
Factorial(
int
nOp1) = 0;
26.
virtual
int
Fabonacci(
int
nOp1) = 0;
27.
};
28.
#endif
此文件首先 #include 将 IUnknown 接口定义文件包括进来。
接下来定义了两个接口,GUID(Globally Unique Identifier全局唯一标识符)它能保证时间及空间上的唯一。
ISmipleMath接口里定义了四个方法,而IAdvancedMath接口里定义了二个方法。这些方法都是虚函数,而整个 ISmipleMath 与 IAdvancedMath 抽象类就作为二进制的接口。
2.2 math.h文件
01.
#include "interface.h"
02.
03.
class
CMath :
public
ISimpleMath,
04.
public
IAdvancedMath
05.
{
06.
private
:
07.
ULONG
m_cRef;
08.
09.
private
:
10.
int
calcFactorial(
int
nOp);
11.
int
calcFabonacci(
int
nOp);
12.
13.
public
:
14.
//IUnknown Method
15.
STDMETHOD(QueryInterface)(REFIID riid,
void
**ppv);
16.
STDMETHOD_(
ULONG
, AddRef)();
17.
STDMETHOD_(
ULONG
, Release)();
18.
19.
// ISimpleMath Method
20.
int
Add(
int
nOp1,
int
nOp2);
21.
int
Subtract(
int
nOp1,
int
nOp2);
22.
int
Multiply(
int
nOp1,
int
nOp2);
23.
int
Divide(
int
nOp1,
int
nOp2);
24.
25.
// IAdvancedMath Method
26.
int
Factorial(
int
nOp);
27.
int
Fabonacci(
int
nOp);
28.
};
此类为实现类,他实现了ISmipleMath和IAdvancedMath两个接口类(当然也可以只实现一个接口类)。
请注意:m_cRef 是用来对象计数用的。当 m_cRef 为0组件对象应该自动删除。
2.3 math.cpp文件
01.
#include "interface.h"
02.
#include "math.h"
03.
04.
STDMETHODIMP CMath::QueryInterface(REFIID riid,
void
**ppv)
05.
{
// 这里这是实现dynamic_cast的功能,但由于dynamic_cast与编译器相关。
06.
if
(riid == IID_ISimpleMath)
07.
*ppv =
static_cast
(
this
);
08.
else
if
(riid == IID_IAdvancedMath)
09.
*ppv =
static_cast
(
this
);
10.
else
if
(riid == IID_IUnknown)
11.
*ppv =
static_cast
(
this
);
12.
else
{
13.
*ppv = 0;
14.
return
E_NOINTERFACE;
15.
}
16.
17.
reinterpret_cast
(*ppv)->AddRef();
//这里要这样是因为引用计数是针对组件的
18.
return
S_OK;
19.
}
20.
21.
STDMETHODIMP_(
ULONG
) CMath::AddRef()
22.
{
23.
return
++m_cRef;
24.
}
25.
26.
STDMETHODIMP_(
ULONG
) CMath::Release()
27.
{
28.
ULONG
res = --m_cRef;
// 使用临时变量把修改后的引用计数值缓存起来
29.
if
(res == 0)
// 因为在对象已经销毁后再引用这个对象的数据将是非法的
30.
delete
this
;
31.
return
res;
32.
}
33.
34.
int
CMath::Add(
int
nOp1,
int
nOp2)
35.
{
36.
return
nOp1+nOp2;
37.
}
38.
39.
int
CMath::Subtract(
int
nOp1,
int
nOp2)
40.
{
41.
return
nOp1 - nOp2;
42.
}
43.
44.
int
CMath::Multiply(
int
nOp1,
int
nOp2)
45.
{
46.
return
nOp1 * nOp2;
47.
}
48.
49.
int
CMath::Divide(
int
nOp1,
int
nOp2)
50.
{
51.
return
nOp1 / nOp2;
52.
}
53.
54.
int
CMath::calcFactorial(
int
nOp)
55.
{
56.
if
(nOp <= 1)
57.
return
1;
58.
59.
return
nOp * calcFactorial(nOp - 1);
60.
}
61.
62.
int
CMath::Factorial(
int
nOp)
63.
{
64.
return
calcFactorial(nOp);
65.
}
66.
67.
int
CMath::calcFabonacci(
int
nOp)
68.
{
69.
if
(nOp <= 1)
70.
return
1;
71.
72.
return
calcFabonacci(nOp - 1) + calcFabonacci(nOp - 2);
73.
}
74.
75.
int
CMath::Fabonacci(
int
nOp)
76.
{
77.
return
calcFabonacci(nOp);
78.
}
79.
CMath::CMath()
80.
{
81.
m_cRef=0;
82.
}
此文件是CMath类定义文件。
2.4 simple.cpp文件
01.
#include "math.h"
02.
#include
03.
04.
using
namespace
std;
05.
06.
int
main(
int
argc,
char
* argv[])
07.
{
08.
ISimpleMath *pSimpleMath = NULL;
//声明接口指针
09.
IAdvancedMath *pAdvMath = NULL;
10.
11.
//创建对象实例,我们暂时这样创建对象实例,COM有创建对象实例的机制
12.
CMath *pMath =
new
CMath;
13.
14.
//查询对象实现的接口ISimpleMath
15.
pMath->QueryInterface(IID_ISimpleMath, (
void
**)&pSimpleMath);
16.
if
(pSimpleMath)
17.
cout <<
"10 + 4 = "
<< pSimpleMath->Add(10, 4) << endl;
18.
19.
//查询对象实现的接口IAdvancedMath
20.
pSimpleMath->QueryInterface(IID_IAdvancedMath, (
void
**)&pAdvMath);
21.
if
(pAdvMath)
22.
cout <<
"10 Fabonacci is "
<< pAdvMath->Fabonacci(10) << endl;
23.
24.
pAdvMath->Release();
25.
pSimpleMath->Release();
26.
return
0;
27.
}
此文件相当于客户端的代码,首先创建一个CMath对象,再根据此对象去查询所需要的接口,如果正确得到所需接口指针,再调用接口的方法,最后再将接口的释放掉。
2.5 Math组件的二进制结构
2.6 小结
此例子从严格意义上来并不是真正的COM组件(他不是dll),但他已符合COM的最小要求(实现IUnknown接口)。接下来我们来做一COM dll(但还不用ATL)。
(待续)