[cpp]
typedef struct {
IExampleVtbl * lpVtbl;
DWORD count;
char buffer[80];
} IExample;
所以下面是一个分配内存并初始化IExample的例子(当然,包括IExampleVtbl):
[cpp]
// 由于IExample_Vtbl的内容永远不会改变,
// 所以我把它定义为静态的并且用以下方法初始化它。
// 它可以被大量的IExample实例复制。
static const IExampleVtblIExample_Vtbl = {SetString, GetString};
IExample * example;
// 创建 (分配内存) 一个IExample结构.
example = (IExample*)GlobalAlloc(GMEM_FIXED, sizeof(IExample));
// 初始化IExample(也就是把指向IExample_Vtbl赋值给它).
example->lpVtbl =&IExample_Vtbl;
example->count = 1;
example->buffer[0] =0;
接着可以这样调用我们的函数:
[cpp]
char buffer[80];
example->lpVtbl->SetString("Sometext");
example->lpVtbl->GetString(buffer,sizeof(buffer));
此外需要说明的是,在我们的函数中可能需要通过访问结构中的“count”和“buffer”成员来调用他们。所以我们要做的是总要把指向这个结构的指针作为第一个参数传入。让我们重写我们的函数来达到这一点:
[cpp]
typedeflong SetStringPtr(IExample *, char *);
typedeflong GetStringPtr(IExample *, char *, long);
longSetString(IExample *this, char * str)
{
DWORD i;
// 把传入的str拷贝到IExample的buffer中
i = lstrlen(str);
if (i > 79) i = 79;
CopyMemory(this->buffer, str, i);
this->buffer[i] = 0;
}
long GetString(IExample*this, char *buffer, long length)
{
DWORD i;
// 拷贝IExample的buffer到传入的buffer中
i = lstrlen(this->buffer);
--length;
if (i > length) i = length;
CopyMemory(buffer, this->buffer, i);
buffer[i] = 0;
return(0);
}
当调用IExample结构的函数时把它的结构指针传入:
[cpp] view plaincopy
example->lpVtbl->SetString(example,"Some text");
example->lpVtbl->GetString(example,buffer, sizeof(buffer));
如果你曾经用过C++,你可能认为:等一下,它好像很眼熟啊。是的,我们上边做的就是用标准C来创建一个C++类。IExample结构实际上是一个C++类(一个不继承于 其他任何类的类)。一个C++类实际上除了第一个成员总是一个数组指针,这个数组包含所有类成员函数的指针,与结构没什么差别。并且每个函数的第一个参数总是类(也就是结构)本身的指针。(它也就是隐藏的this指针)
简单说来,一个COM对象实际上就是一个C++类。你现在可能会认为:“哇噻!IExample现在就是一个COM对象嘛?这就是它的全部嘛??它就这么简单!”打住!IExample正在接近这一点,但对于它还有很多,它不会这么容易。如果它是这样,它就不会是微软技术了,现在做什么?
首先,让我先来介绍一下COM术语。你看到上面的指针数组-IExampleVtbl结构了嘛?COM文档中把它定义为接口或虚表。
一个COM对象在虚表(也就是我们的IExampleVtbl结构)中首先需要有三个被命名为QueryInterface、AddRef和Release的函数。 当然,我们也必须写这三个函数。微软已经把这三个函数的调用参数,返回值和调用约定指定好了。我们需要#include一些微软的包含文件(他们在你的C编译器包中,或者你下载的微软的SDK中)。我们这样重新定义我们的IExampleVtbl结构:
[cpp]
#include
#include
#include
typedefHRESULT STDMETHODCALLTYPE QueryInterfacePtr(IExample *, REFIID, void **);
typedef ULONGSTDMETHODCALLTYPE AddRefPtr(IExample *);
typedef ULONGSTDMETHODCALLTYPE ReleasePtr(IExample *);
typedef struct {
// 前3个成员必须叫是QuryInterface、AddRef和Release
QueryInterfacePtr *QueryInterface;
AddRefPtr *AddRef;
ReleasePtr *Release;
SetStringPtr *SetString;
GetStringPtr *GetString;
} IExampleVtbl;
让我们查看typedef过的QueryInterface。首先,这个函数返回一个HRESULT,它被简单定义为LONG。接着,它用了STDMETHODCALLTYPE。这意味参数不通过寄存器传递,而是通过栈。并且也约定了谁来平栈。事实上,对于COM对象,我们应该确保所有我们的函数都被定义为STDMETHODCALLTYPE,并返回一个LONG(HRESULT)。QueryInterface的第一个参数是用于函数调用的对象指针。我们难道不是在把IExample转化为一个COM对象嘛?是 的,这也是我们要传递的参数的原因。(记住确保传递给我们函数的第一个参数是一个用于调用这些函数的结构指针?COM完全强制依赖以上的定义)
稍后,我们展示一个REFIID是什么,并且也提到QueryInter