荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Deny (中秋快乐!!!!!!!!!!!!!!!), 信区: Program
标  题: COM专题---组件、COM和ATL(3)
发信站: 荔园晨风BBS站 (Wed Sep 26 22:10:00 2001), 转信

COM专题---组件、COM和ATL(3)


获取对象和接口

     在第二部分,我们讨论了 COM 中两个非常基本的概念:对象和接口。我们还
演示了一个 对象如何实现多个接口。最后,我们讨论了 COM 方法程序调用的本质
,并且认识到它们 与 C++ 虚函数的调用方式相同。(但是,我们也注意到,您可
以在任何支持函数指针数 组的指针的语言中(或者通过调用汇编语言来支持)调
用 COM 的方法程序。)

     有关 COM 的详细讨论:对象的创建和破坏;获得接口指针 但是,您可能会
发现这些讨论有些不能令人满意:我们从来没有讨论如何创建对象,也没 有讨论
如何获得接口指针,以调用对象接口的方法程序。而且,我们从来没有讨论如何删
 除您不再需要的对象,或者如何切换接口。

     医生将在本周的讨论中涉及这些主题。但是,首先,我们需要稍微整理一下
思路。您是否 有过这样的经历:想要解释什么,但是突然想起来您忘记了一些重
要的东西。现在就是这 种情况,医生忘记的事情是,为了创建对象,需要有一种
能引用它们的方法,还要有一种 能明确定义接口的方法。所以,我们先讨论被遗
忘的东西,然后再涉及我们真正感兴趣的 内容。(而且,这也是为什么这次的栏
目这么长,并且这么晚与大家见面。)

     COM 中的标识符

     如果您考虑得比较超前,您会知道,我们需要一些标识符来表示 COM 世界中
的各种实体 。首先,对象类型(或称“类”)需要一个标识符。其次,各接口也
需要标识符。但是, 我们应该用什么作为标识符呢?32 位整数?64 位整数?我
们可以用它们作标识符。但是 有一个问题:在所有的计算机上,标识符都必须是
独一无二的,因为无法知道会在什么计 算机上安装组件。对象和接口需要在所有
的计算机上都使用相同的标识符,这样才能使任 何客户程序都可以使用该组件。
另外,不能有其他对象或接口使用这个标识符,不管其他 对象或接口来自何处。
也就是说,这些标识符必须是全球唯一的。

     幸运的是,创建这种标识符的算法和数据格式是存在的。通过使用计算机的
唯一网络卡 ID、当前时间和其他数据,一个称为 GUIDGEN.EXE 的程序就可以创建
这种标识符,称为 GUID(全球唯一标识符)。GUID 是按 16 字节(128 位)结构
存储的,这样就可以有 2128 个可能的 GUID。但是,不要担心会用完所有的
GUID:虽然医生不知道整个宇宙中 的原子的确切数目,即使搜索了 Web 也未能找
到答案,但是他相信那个数目一定比 2128 少得多。所以,GUID 不会缺乏,也没
必要保留 GUID,不用理会 COM 从业者的玩笑 。

     在 C++ 中,COM 头文件为 GUID 定义了数据类型,即 CLSID(类标识符
GUID)和 IID( 接口标识符 GUID)。由于这些 16 字节结构有些大,因而不能按
值传递,所以当传递 GUID 时,需要使用 REFCLSID 和 REFIID 作为参数的数据类
型。您需要为每个对象类型 GUID 时,需要使用 REFCLSID 和 REFIID 作为参数的
数据类型。您需要为每个对象类型 创建一个 CLSID,为每个自定义接口创建一个
 IID。

     标准接口

     COM 定义了大量的标准接口及其相关的 IID。例如,所有接口的母辈
IUnknown 的 IID 是 "00000000-0000-0000-c000-000000000046"(连字符是书写
 GUID 的标准方式)。这 个 IID 是由 COM 定义的,您永远也不必直接引用它;
而应使用 IID_IUnknown 常量,这 是在头文件中定义的。

     IUnknown 接口有三个函数:

     HRESULT QueryInterface(REFIID riid, void **ppvObject);

     ULONG AddRef();

     ULONG Release();

     稍后我们将详细讨论这些函数的功用。

     使用标准接口可以完成大量 COM 编程工作——其中的大部分工作就是提供标
准接口的实 现细节,这样,其他的 COM 客户程序和对象就可以使用您的对象了。


     顺便说一句,在 COM 中用到了一些宏,以说明函数的返回值和调用约定。几
乎所有的 COM 方法程序都返回一个 HRESULT 类型的量,所以 STDMETHODIMP 宏就
采用这种类型。 STDMETHODIMP_() 宏带有一个参数——方法程序的返回类型。(
只有当您使用纯虚函数定 义接口时,才会用到 STDMETHOD 宏——并且将由 IDL
编译器为您编写代码,有关的详细 内容稍后讲解。)

     使用这些宏,上面的声明就会是这样:

     STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release();

     以后我们将一直使用这些宏。这样做,就可以容易地将代码移植到其他不同
的 COM 平台 (例如 Macintosh 和 Solaris)上。

     自定义接口

     自定义接口是您创建的接口。您要为这些接口创建您自己的 IID,并且定义
您自己的函数 。我们的 IFoo 接口就是一个自定义接口。通过运行我计算机上的
 GUID 生成器,我已经 定义了一个 IID,称为 IID_Ifoo(它的值是
"13C0205C-A753-11d1-A52D-0000F8751BA7" )。

     回忆一下,原来的类声明是:

     class IFoo { virtual void Func1(void) = 0; virtual void Func1(void)
 = 0; virtual void Func2(int nCount) = 0; };

     我们将略作修改,就将它变成 COM 兼容的:

     Interface IFoo : IUnknown { virtual HRESULT STDMETHODCALLTYPE
Func1(void) = 0; virtual HRESULT STDMETHODCALLTYPE Func2(int nCount) =
0; };

     使用上面所说的宏,就变成:

     Interface IFoo : IUnknown { STDMETHOD Func1(void) PURE; STDMETHOD
Func2(int nCount) PURE; };

     "Interface" 并不是 C++ 中的关键字,而是在相应的 COM 头文件中用
#define 定义为 "struct" 的。(回忆一下,在 C++ 中,类和结构是相同的,只
是在默认情况下,结构使 用公共继承和访问,而不是私有的。)STDMETHOD 使用
 STDMETHODCALLTYPE,它定义为 __stdcall,这表明编译器要为这些函数生成标准
的函数调用序列。记住,我们使用这些 宏是因为将我们的代码移植到不同的平台
上时,它们的定义会改变。

     所有的 COM 函数(几乎毫无例外)都返回一个 HRESULT 类型的错误代码。
这个 HRESULT 是一个 32 位的数值,它使用符号位代表成功或失败,其余 31 位
中的域表明“ 功能”和与功能对应的错误代码,还有一些保留位。通常情况下要
返回成功代码 S_OK, 但是如果在方法程序中遇到了问题,也可以返回错误代码—
—或者是标准的,或者是您自 己构造的。

     最后,注意我是从标准 COM 接口 IUnknown 派生出 IFoo 的。这意味着,任
何实现 IFoo 的类也需要同时实现 AddRef、Release 和 QueryInterface 这三个
函数。另外,在 IFoo 的虚函数表中,这三个函数的指针将位于 Func1 和
Func2 函数的指针之前。在虚 函数表中有五个函数,而这五个函数都需要实现。
所有的 COM 接口都是从 IUnknown 派 生的,所以所有的 COM 接口除包含其他函
数外,都包含这三个函数。

     MIDL 又怎样?

     您不用自己动手编写上面的声明 — 它是由 MIDL 编译器为您生成的。为什
么?正如事实 表明的,C++ 不能表达需要在一个接口中表达的所有东西。回忆一
下,COM 对象可以是进 程内使用的 DLL,意味着位于相同的地址空间。所以,如
果您将某些数据的一个指针传递 到一个进程内服务程序,该服务程序会直接废弃
该指针。

     但是,也要记住,您的 COM 对象也可以是一个本地(线外)服务程序,位于
单独的 EXE 地址空间,甚至可以远程访问。每当您向这样一个对象的 COM 方法程
序中传递一个指针 时,都会遇到问题:在任何其他地址空间,该指针都是毫无意
义的,有意义的是该指针所 指向的数据。该数据必须复制到其他地址空间—甚至
复制回去。这个复制正确数据的过程 称为排队 (marshalling)。谢天谢地,在大
多数情况下 COM 为您进行排队。但是,为此 您不仅需要告诉 COM 指针所指数据
的类型,您还需要告诉 COM 该指针是如何使用的。例 如,该指针是否指向一个数
组?指向一个字符串?该参数只是一个输入参数?是一个输出 参数?还是两者都
是?您会看到,无法在 C++ 中表达这些。

     所以,我们需要另一种语言,称为 IDL (Interface Definition Language)
,以便定义接 口。IDL 很象 C++,但是将方括号中的“属性”添加到与 C++ 类似
的代码中。MIDL.EXE 编译您(或 Visual Studio)编写的 IDL 文件,以便生成各
种输出。到目前为止,我们 关心的唯一输出是我们的接口的头文件,我们将在我
们的代码中包含这个头文件。

     在我们的示例中,并没有多少区别,因为我们只是按值传递参数,所以
IDL 代码很眼熟 —主要区别是没有 "virtual" 一词。但是,如果我们创建一个新
的接口 IFoo2,除了其 他两个方法程序,还添加了一个方法程序 Func3(int *),
IDL 会是这样:

     [ uuid(E312522F-A7B7-11D1-A52E-0000F8751BA7) ] Interface IFoo2 :
IUnknown { HRESULT Func1(); HRESULT Func2(int in_only); HRESULT
Func3我是Deny,我怕谁!^_^这年头 MM 大嗮!!!!!!!!!!

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.1.201]

--
精 金


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店