COM in plain C (1 VTable)By Jeff Glatt

 在我们学习使用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的函数需要访问IExmaplecountbuffer成员变量。那么我们可以传递一个指向此结构的指针作为第一个参数给函数。我们重写这些函数以实现此功能:

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 对象里的其它的函数是什么。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值