用标准C编写COM(一)(八)

2014-11-24 09:46:32 · 作者: · 浏览: 9
xample函数。它看起来是这个样子的:
[cpp]
#undef INTERFACE
#defineINTERFACE IExample
DECLARE_INTERFACE_(INTERFACE, IUnknown)
{
STDMETHOD (QueryInterface) (THIS_ REFIID,void **) PURE;
STDMETHOD_ (ULONG, AddRef) (THIS) PURE;
STDMETHOD_ (ULONG, Release) (THIS) PURE;
STDMETHOD (SetString) (THIS_ char *)PURE;
STDMETHOD (GetString) (THIS_ char *,DWORD) PURE;
};
它可能看起来有点怪怪的。
当在定义一个函数时,只要函数返回HRESULT就要使用STDMETHOD。我们的QueryInterface、SetString和GetString函数返回的是HRESULT。AddRef和Release不是。后面的两个返回ULONG。这就是我们用STDMETHOD_(以下滑线结尾)替换它们两个的原因。然后我们把函数名字放入括号中。如果函数不返回HRESULT,我们需要放入它的返回值的类型,然后在这个函数名前加一个逗号。在函数名后,在括号中列出函数的参数。THIS是指向我们对象(也就是IExample)的一个指针。如果传给函数的仅仅是这个指针,那么你只需简单的把THIS放在括号中。AddRef和Release函数就是这种情况。但其它函数有额外的参数。所以我们必须用THIS_(以下划线结尾)。然后我们列出剩余参数。注意在THIS_和剩余参数之间没有逗号。但是每个剩余参数之间有逗号。最后,我们放一个PURE字和分号。
当然,这是一个不可思议的宏,通过这种方法定义COM对象,普通的C编译器和C++编译器都可以正常工作。
“但我们的IExample结构定义在哪里?”。你可能这么问。这个宏确实非常不可思议。它使C编译器自动生成只包含“lpVtbl”成员的IExample结构定义。所以仅通过这个方法定义我们的虚表,我们自动获得适合其它程序员的IExample定义。
粘贴我们的两个GUID宏到包含文件中,一切准备就绪。我这样创建了IExample.h文件。
但你知道,我们的IExample事实上有两个数据成员。所以我们必须做的是在我们的DLL源文件中定义一个IExample的“异型”。我们叫它“MyRealIExample”,它是我们的IExample的真正的定义。
[cpp]
typedef struct {
IExampleVtbl *lpVtbl;
DWORD count;
char buffer[80];
} MyRealIExample;
我们在IClassFactory的CreateInstance中改变一行以便分配MyRealIExample结构:
[cpp]
if (!(thisobj =GlobalAlloc(GMEM_FIXED, sizeof(struct MyRealIExample))))
其它程序不需要知道我们给它的对象内部的额外数据成员(对其他程序隐藏有实际的目的)。毕竟,这两个结构拥有指向同一函数指针数组的同一“lpVtbl”。但现在我们的DLL函数可以通过铸造一个指向MyRealIExample指针的IExample指针来访问这些“隐藏的”成员。
定义文件(DEF)
我们也需要一个DEF文件来暴露DllCanUnloadNow和DllGetClassObject这两个函数。微软的编译器要求把它们定义为PRIVATE。这是我们的DEF文件,链接器依赖它:
[cpp]
LIBRARY IExample
EXPORTS
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
安装DLL和注册对象
现在我们完成了构造IExample.dll所需的每件事。我们继续来编译IExample.dll。
但我们的工作还没完。在其它程序可以使用IExample对象(也就是这个DLL)前,我们需要做两件事情:
安装DLL在其它程序运行计算机的可以找到的地方。
将DLL作为COM组件注册
我们需要创建一个拷贝IExample.DLL到一个适当位置的安装程序。例如,或许我们在ProgramFiles目录下创建一个“IExample”的目录,把DLL拷贝到那。(当然我们的安装程序要做版本检查,这样如果我们的DLL的一个后续版本在那已经安装过了,我们就不用旧版本来覆盖它。)
我们接着需要注册这个DLL。这包括创建几个注册表键值。
我们首先需要在HKEY_LOCAL_MACHINE\Software\Classes\CLSID下创建一个键值。我们必须用我们的IExample对象的GUID作为这个新键值的名字,,而且它必须是一个特殊格式的文本字符串。
如果你下载了例程,在RegIExample目录下包含了一个IExample.dll安装程序。StringFromCLSID函数给出怎样把我们的IExample的GUID格式化成一个适合于用它来创建注册表键值名的文本字符串。
注意:这个安装例程在注册DLL前不拷贝DLL到适当的位置。这比较合理,它允许你选择任何地方来编译你的IExample.dll和注册它。这对于开发、测试来说更方便。一个优秀的安装程序产品应该把DLL拷贝到合适的位置,并且做版本检查。这些需要增加部分留给你来做你自己的安装程序。
在我们的“GUID键”下,我们必须创建一个名叫的InprocServer32子键。这个子键的缺省值要设置成我们的DLL安装的全路径。
如果我们不需要限制调用我们DLL函数的程序必须是单线程,我们还必须设置一个命名为ThreadingModel的值为“both”字符串值。由于我们在IExample函数没有用到全局数据,我们是线程安全的。
运行完我们的安装程序后,IExample.dll现在已经作为一个COM组件注册在我们的计算机上了,现在其它程序可以使用它了。
注意:UnregIExample目录里包含一个反安装IExample.dll的例子。它只删除RegIExample创建的注册表键值项。一个优秀的反安装产品应该删除IExample.dll和安装程序创建的目录。
C实例程序
现在我们准备写一个使用我们IExmapleCOM对象的C程序。如果你下载了例程,IexampleApp目录包含一个C例子程序。
首先,C程序要#include我们的IExample.h包含文件,这样它可以查询到IExample对象和它的虚表的GUID。
一个程序在使用COM对象前,它必须通过调用CoInitialize函数来初始