在我们学习使用 COM 对象之前,我们首先需要知道 COM 对象是什么。最后的学习方式是建立一个我们自己的 COM 对象
在我们建立
COM
对象之前。我们先看看
C struct
数据类型。作为一个
C
程序员,你肯定很熟悉
struct
。下面定义了
IExample struct,
它有两个成员变量:一个是
DWORD(
可通过成员名
count
来访问
),
另一个是
char
数组(可通过成员名
buffer
来访问)
struct
IExample {
DWORD count;
char
buffer[
80
];
};
typedef
struct
{
DWORD count;
char
buffer[
80
];
} IExample;
下面是为此结构建立实例(忽略错误检查),并且初如化其成员。
IExample * example;
example = (IExample *)GlobalAlloc(GMEM_FIXED,
sizeof
(IExample));
example-
>
count =
1
;
example-
>
buffer[
0
] =
0
;
你知道可以使用
struct
来保存一个指向函数的指针吗?想必你肯定知道。下面是这样的一样例子。假设我们有一个指向函数的指针,此函数接收一个
char
指针类型的参数,并且返回
long
型的参数:函数定义如下:
long
SetString(
char
* str)
{
return
(
0
);
}
现在我们想在
IExmaple
里保存一个指向此函数的指针。我们如下定义
IExmaple
,添加一个成
SetString
以保存一个指向上面函数的指针(我使用了
typedef
以便阅读)
typedef
long
SetStringPtr(
char
*);
typedef
struct
{
SetStringPtr * SetString;
DWORD count;
char
buffer[
80
];
} IExample;
下面是我们如何使用在
IExample
里保存的函数指针调用
SetString
函数。
example-
>
SetString = SetString;
long
value
= example-
>
SetString(
"Some text"
);
好了,或许我们想再保存指向第二个函数的指针。下面是第二个函数。
long
GetString(
char
*buffer,
long
length)
{
return
(
0
);
}
我们重新定义
IExmaple
,并且添加另一成员
GetString
保存指向第二个函数的指针。
typedef
long
GetStringPtr(
char
*,
long
);
typedef
struct
{
SetStringPtr * SetString;
GetStringPtr * GetString;
DWORD count;
char
buffer[
80
];
} IExample;
并且初始化成员
example-
>
GetString = GetString;
现在假设我们不直接在
IExample
里保存这些函数指针。我们想使用一个函数指针数组。例如,定义一个
struct
,它的目的仅是保存我们的两个函数指针。我们把这个结构叫做
IExampleVtbl,
像下面这样定义:
typedef
struct
{
SetStringPtr * SetString;
GetStringPtr * GetString;
} IExampleVtbl;
现在我们可在
IExmaple
里保存一个上面这样的指针数组。添加一个成员
lpVtbl
来完成此功能(当然,我们去除
SetString
和
GetString
成员,因为他们已经被移到
IExmapleVtbl
结构里了)
typedef
struct
{
IExampleVtbl * lpVtbl;
DWORD count;
char
buffer[
80
];
} IExample;
下面是如何为
IExmaple
分配空间并且初使化成员
(
当然也包括
IExampleVtbl
结构
)
// Since the contents of IExample_Vtbl will never change, we'll
// just declare it static and initialize it that way. It can
// be reused for lots of instances of IExample.
static
const
IExampleVtbl IExample_Vtbl = {SetString, GetString};
IExample * example;
// Create (allocate) a IExample struct.
example = (IExample *)GlobalAlloc(GMEM_FIXED,
sizeof
(IExample));
// Initialize the IExample (ie, store a pointer to
// IExample_Vtbl in it).
example-
>
lpVtbl = &IExample_Vtbl;
example-
>
count =
1
;
example-
>
buffer[
0
] =
0
;
这样调用我们的函数:
char
buffer[
80
];
example-
>
lpVtbl-
>
SetString(
"Some text"
);
example-
>
lpVtbl-
>
GetString(buffer,
sizeof
(buffer)); 进一步讲,假设被调用结构 IExmapleVtbl 的函数需要访问 IExmaple 的 count 和 buffer 成员变量。那么我们可以传递一个指向此结构的指针作为第一个参数给函数。我们重写这些函数以实现此功能:
Collapse
typedef long SetStringPtr(IExample *, char *);
typedef long GetStringPtr(IExample *, char *, long );
long SetString(IExample * this , char * str)
{
DWORD i;
// Let's copy the passed str to IExample's buffer
i = lstrlen(str);
if (i > 79 ) i = 79 ;
CopyMemory(this - > buffer, str, i);
this - > buffer[i] = 0 ;
return ( 0 );
}
long GetString(IExample * this , char *buffer, long length)
{
DWORD i;
// Let's copy IExample's buffer to the passed buffer
i = lstrlen(this - > buffer);
--length;
if (i > length) i = length;
CopyMemory(buffer, this - > buffer, i);
buffer[i] = 0 ;
return ( 0 );
}
我们调用函数的时候传递
IExmaple
结构的指针:
example-
>
lpVtbl-
>
SetString(example,
"Some text"
);
example-
>
lpVtbl-
>
GetString(example, buffer,
sizeof
(buffer));
如果你曾经用过
C++
的话,你或许会想,等等,这看起来很熟悉。是的。我们就是使用
C
来建立
C++
类。
IExample
结构是一个真正的
C++
类
(
不是从其它类继承而来的
)
。一个
C++
类不过是一个
struct
,只不过其第一个成员是指向一个数组——这个数组包括了类里定义的所有函数。传向这些函数的所有第一个参数都是指向这个类的指针(参见第三部分:“
this”
指针)
简单来说,一个
COM
对象其实只是一个
C++
类。你或许会想:哇?
IExamle
现在是一个
COM
对象?这就是所有关于
COM
对象的东西?那太容易了!慢着,
IExmaple
只是接近了,但是会有更多的东西,不是这样简单的。如果这么简单就不是“微软技术”了
首先我们介绍一些
COM
技术术语。你已经看到上面的
IExmapleVtble
结构了。
COM
文档把它称作为接口
interface
或者
VTable
.
一个
COM
对象的需要的是它的前三个
VTable
成员
(
像我们的
IExampleVtbl
结构
)
必须是
QueryInterface
,
AddRef
,
和
R
elease
。当然接下面我们会写这些函数。
Microsoft
规定了什么必须传递给这些函数,返回值是什么和他们使用什么调用规范)我们需要
#include
一些
Microsoft
头文件
(
这些头文件或许你的
c
编译器自带,或许可以从
Microsoft SDK
里下载
)
我们重定义
IExampleVtbl
如下:
#include <
windows.h
>
#include <
objbase.h
>
#include <
INITGUID.H
>
typedef
HRESULT STDMETHODCALLTYPE QueryInterfacePtr(IExample *, REFIID,
void
**);
typedef
ULONG STDMETHODCALLTYPE AddRefPtr(IExample *);
typedef
ULONG STDMETHODCALLTYPE ReleasePtr(IExample *);
typedef
struct
{
// First 3 members must be called QueryInterface, AddRef, and Release
QueryInterfacePtr *QueryInterface;
AddRefPtr *AddRef;
ReleasePtr *Release;
SetStringPtr *SetString;
GetStringPtr *GetString;
} IExampleVtbl;
我们看看
QueryInterface
的
typedef
。首先,函数返回
HRESULT
。
HRESULT
定义为
long
。接着,它使用
STDMETHODCALLTYPE
,这意味着它不使用寄存器传递调用参数,而是使用
stack
,并且明确了谁来清栈。实际上对于
COM
对象来说,我们所有的函数都申明了
STDMETHODCALLTYPE
,并且返回
long
(
HRESULT
)。传递给
QueryInterface
的第一个参数是一个指向对象的指针,使用这个对象来调用函数。难道我们没有把
IExmaple
转换成一个
COM
对象了吗?是的,我们将要传递一个指向这个结构的指针。(回忆一下我们把第一个参数作为一个指向这个
struct
的指针。
COM
只是强迫并且依靠这种设计)
然后我们看看
REFIID
是什么,并且讨论
QueryInterface
的第三个参数用于做什么。注意现在
AddRef
和
Release
传递了指向同一个
struct
的指针。
typedef
HRESULT STDMETHODCALLTYPE SetStringPtr(IExample *,
char
*);
typedef
HRESULT STDMETHODCALLTYPE GetStringPtr(IExample *,
char
*,
long
);
HRESULT STDMETHODCALLTYPE SetString(IExample *
this
,
char
* str)
{
...
return
(
0
);
}
HRESULT STDMETHODCALLTYPE GetString(IExample *
this
,
char
*buffer,
long
value
)
{
...
return
(
0
);
}
总之,一个
COM
对象基本就是一个
C++
类。一个
C++
类总是以指向
VTable
(一组函数指针)的指针开头。必须以
QueryInterface
、
AddRef
、
Release
作为前三个函数,你决定你的
COM
对象里的其它的函数是什么。