荔园在线

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

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


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

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


接口

     首先,在 IDL 中有各种属性包含在方括号中。属性总是应用于后面紧 跟的
内容,所以上面的 UUID 属性应用于接口—它是接口的 IID。(UUID 或通用唯一
标 识符,是 GUID 的同义词。)[in, out] 属性应用于指针,并且告诉 COM 当调
用 Func3 时,必须让单个的 int 在函数内外排队(如果需要排队)。如果 int
指针引用一个数组 ,它会有一个附加属性(size_is 带一个参数)。也有 IDL 代
码定义对象,定义我们的 对象的代码段可能会象这样:

[ uuid(E312522E-A7B7-11D1-A52E-0000F8751BA7) ]
coclass Foo
{
[default] Interface IFoo;
};

     这就是 CLSID 如何与类相关的,也是类实现的接口如何定义的。注意,虽然
这些代码很 象 C++,并且带有一些附加属性,但是这些代码并不象接口定义那样
严格地对应于 C++ 代码。

     对象的创建

     一旦我们的 CLSID 与一个对象类型相关(将在稍后详细探讨),就可以创建
一个对象。 正如所证明的,这很简单—只是一个函数的调用:

IFoo *pFoo = NULL;
HRESULT hr = CoCreateInstance(CLSID_Foo, NULL, CLSCTX_ALL,
IID_IFoo, (void **)&pFoo);

     如果 CoCreateInstance 成功了,它会创建 CLSID GUID CLSID_Foo 标识的
对象的一个实 例。注意,没有“对象的指针”这回事;相反,我们总是通过一个
接口指针引用对象。所 以我们必须指定我们需要哪个接口 (IID_IFoo),并将一个
指针传递到某处,以便让 CoCreateInstance 存储该接口指针。

     我们还没有讨论的两个参数目前还不重要。一旦我们调用了一个函数,需要
进行检查,以确保调用成功,并且接下来使用该对象:

if (SUCCEEDED(hr)) {
pFoo->Func1(); //调用方法程序。
pFoo->Func2(5);
pFoo->Func2(5);
pFoo->Release(); // 当处理完之后,必须释放接口。
}
else // 创建失败...

     CoCreateInstance 返回一个 HRESULT,以表明它是否成功。因为非负数表示
成功,我们 总是使用 SUCCEEDED 宏来检查结果。实际上,最普通的成功代码
S_OK 是零,所以象 "if (hr) // Success" 这样的检查根本不起作用。一旦成功
地创建了该对象,您可以如 上所示,使用接口指针来调用接口的方法程序。

     当您处理完接口指针后,需要通过调用 Release 来释放该接口指针,这是至
关重要的。 注意,由于所有接口都是从 IUnknown 导出的,所以所有接口都支持
 Release。当您告诉 COM 对象您已经处理完它时,由它自己释放自己,但是它需
要您告诉它您何时处理完。 如果您忘记了调用 Release,该对象会被泄漏(并且
被锁在内存中,至少要等到关闭应用 程序,甚至要等到系统重新启动)。弄乱对
象生命期是非常普遍的 COM 编程问题,而且 难于发现。所以,要从现在开始小心
谨慎。注意,如果我们真正创建了某接口,就只能释 放该接口。

     图 5 是我们新创建对象的图例。按约定,IUnknown 没有标识;它总是画在
对象的右上角 。所有其他接口画在左边。

     图 5 第一个简单的 COM 对象,带有无标识的 IUnknown。

     由于我们实现了 IUnknown,所以就有了一个 COM 对象。(就象画一个连接
器一样简单! ) 如果将一个 IFoo2 接口添加到该对象,总共就有了三个接口,
如图 6 所示。 图 6 理论版 2.0,支持 IFoo 和 IFoo2。

     GUID 和注册表

     那么 COM 是如何找到对象的代码,以便创建对象的呢?很简单:它在注册表
中找。当安 装了一个 COM 组件时,它必须在注册表中建立注册项。对于我们的
Foo 类,注册项可能 会是这样:

HKEY_CLASSES_ROOT
CLSID
{E312522E-A7B7-11D1-A52E-0000F8751BA7}="Foo Class"
InprocServer32="D:\\ATL Examples\Foo\\Debug\\Foo.dll"

     大多数对象会有一些附加项,但是我们现在暂时忽略这些项。

     在 HKEY_CLASSES_ROOT\CLSID,有一个我们的类的 CLSID 的注册项。这就是
 CoCreateInstance 如何查找组件的 DLL 名称的。当您为 CoCreateInstance 提
供了 CLSID,它会找到 DLL 名称,加载这个 DLL,并且创建该组件(稍后将详细
讨论)。

     如果服务程序是线外的或远程的,该注册项会有所不同,但是重要的是这些
信息存在,所 以 COM 可以启动服务程序并且创建该对象。

     如果您知道对象的名称 (ProgID),但不知道它的 CLSID,就可以在注册表中
查找 CLSID 。对于我们的对象,有这样一个注册项:

     HKEY_CLASSES_ROOT
Foo.Foo="Foo Class"
CURVER="Foo.Foo.1"
CLSID="{E312522E-A7B7-11D1-A52E-0000F8751BA7}"
Foo.Foo.1="Foo Class"
CLSID="{E312522E-A7B7-11D1-A52E-0000F8751BA7}"


     "Foo.Foo" 是独立版本的 ProgID,Foo.Foo.1 是 ProgID。如果您从 Visual
 Basic 中创 建一个 Foo 对象,可使用其中一个 ProgIDs 查找 CLSID。(注意,
在当前版本,ATL 向 导还不能完全正确地创建注册项:它会漏掉上面所示的前两
个 CLSID 关键字。不要忘记 为独立版本的 ProgID 复制 CLSID。)

     模块、组件类和接口

     注意,一个模块(DLL 或 EXE)有可能(实际上是通常)实现多个 COM 组件
类。如果是 这样,会有多个 CLSID 注册项引用相同的模块。

     这样我们现在可以定义模块、类和接口之间的关系。一个模块(您连编和安
装的基本单元 )可以实现一个或多个组件,每个组件在注册表中都有自己的
CLSID 和指向模块的文件 名的注册项,并且每个组件至少实现两个接口:
IUnknown 和一个提供组件功能的接口。 图 7 表明了这点。

     图 7 模块 Oo.DLL 包含三个对象(Foo、Goo 和 Hoo)的实现细节。每个对
象实现了 IUnknown 和一个或多个附加接口。

     使用 QueryInterface 获得其他接口

     可以说,我们有了一个新的改进了的 Foo2 对象,它实现了两个自定义接口
:IFoo 和 IFoo2。我们已经知道了如何使用 CoCreateInstance 创建这样一个对
象,也知道了如何 获得一个指向三个接口(不要忘记 IUnknown)之一的指针。


     当我们得到这样的接口指针之后,怎样才能得到该对象的其他接口的接口指
针呢?不能再 调用 CoCreateInstance—这会创建一个新对象,这并不是我们希望
的,我们只需要现有 对象的另一个接口。

     这就需要 IUnknown::QueryInterface 来解决了。记住,由于所有的接口都
从 IUnknown 继承,所以它们都执行 QueryInterface。这样,我们只需使用第一
个接口指针来调用 QueryInterface,以便得到第二个接口指针:

IFoo *pFoo = NULL;
HRESULT hr = CoCreateInstance(CLSID_Foo2, NULL,
                CLSCTX_ALL, IID_IFoo, (void **)&pFoo);
if (SUCCEEDED(hr))
{
        pFoo->Func1(); //调用 IFoo::Func1
        IFoo2 *pFoo2 = NULL;
        hr = pFoo->QueryInterface(IID_IFoo2, (void **)&pFoo2);
        if (SUCCEEDED(hr))
        {
                int inoutval = 5;
                pFoo2->Func3(&inoutval);// IFoo2::Func3
                pFoo2->Release();
        }
        pFoo->Release();
}
     我们向 QueryInterface 传递了所需接口的 IID,还有一个指向
QueryInterface 保存新 接口指针的位置的指针。一旦 QueryInterface 返回成功
,我们就可以使用该接口指针来 调用该接口的函数。

     一定要注意,当我们处理完两个接口指针之后必须释放这两个指针。如果释
放其中的一个 指针失败,就会泄漏该对象。由于我们只能通过接口指针引用该对
象,所以必须释放每个 得到的接口指针,这样才能作为一个整体释放该对象。

     IUnknown 的其他函数

     IUnknown 有其他两个函数:AddRef 和 Release。我们已经看到,您使用
Release 告诉 一个对象,您已经处理完一个接口指针。那么您什么时候使用
AddRef 呢?

     引用计数,并且当一个对象可以释放的时候 大多数 COM 对象都保留一个引
用计数 ( reference count)—也就是说,它们需要跟踪有 多少个该对象的接口指
针正在使用。如果所有对象接口的引用计数变成零时,该对象就可 以释放。我们
不必明确地释放该对象;只需释放对象的所有接口指针,对象会在合适的时 候释
放自己。

     AddRef 增加引用计数,而 Release 减少引用计数。所以,如果没有调用
AddRef,为什 么必须调用 Release 呢?

     每当 QueryInterface 为一个对象分配一个新指针时,QueryInterface 有责
任在返回该 指针前调用 AddRef。这就是为什么不必为得到的指针调用 AddRef。
QueryInterface 为 我们做了。(注意,CoCreateInstance 调用
QueryInterface,而 QueryInterface 调用 AddRef,所以对象的第一个接口指针
也是这样。)

     对于调用了 AddRef 的相同接口指针,也需要调用 Release。如果对象需要
,它们可以一 个接口一个接口地跟踪引用。上面的代码小心地做到了这一点,对
应 Release 调用的正 确配对的隐含 AddRef 调用—每个接口指针一个 Release
调用。

     如果您复制了一个接口指针,则需要调用 AddRef,这样该接口的引用计数才
准确。至于 何时需要何时不需要有点复杂,但是各种 COM 参考书都有详细的介绍
。有关细节请查看 这些参考书。

     各种巧妙的指针类使得处理 IUnknown 变得容易多了(实际上是自动的)。
在 ATL 和 Visual C++ 5.0 中有几个这样的类。如果您使用另一种语言,例如
Visual Basic 或 Java,该语言对 COM 的实现会正确地处理 IUnknown 方法程序


     回顾与前瞻

     我们已经讨论了如何创建对象,以及如何破坏它们(不是真的破坏,只是释
放它们的所有 接口指针),还有如何调用接口的方法程序和切换接口。同时,也
介绍了用于标识对象和 接口的各种 GUID 的概念,还有所需的注册项,这样
COM 才能知道如何创建您的对象。

     在第四部分,我们将特别详细讨论如何创建进程内对象,以及使创建过程更
高效的方法。 如果有时间,还将探讨实现一个对象的本质,包括创建一个对象和
 IUnknown 的代码。




     我是Deny,我怕谁!^_^这年头 MM 大嗮!!!!!!!!!!

※ 修改:·Deny 於 Mar 27 18:09:23 修改本文·[FROM: 192.168.1.166]
※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.1.201]
精 金


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

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