Programming 版 (精华区)
发信人: lofe ()感激生活(), 信区: Programming
标 题: 组件、COM和ATL(5)
发信站: 哈工大紫丁香 (Sun Sep 3 08:04:47 2000), 转信
发信人: kywu (太阳星辰), 信区: VisualC
标 题: 组件、COM 和 ATL(5)
发信站: BBS 水木清华站 (Mon Mar 22 13:16:27 1999) WWW-POST
第四部分:对象类和对象库
在第三部分,我们讨论了如何创建对象,以及如何破坏它们(不是真的破坏,只是释放它
们的所有接口指针),还有如何调用接口的方法程序和切换接口。同时,也介绍了用于标
识对象和接口的各种 GUID 的概念,还有所需的注册项,这样 COM 才能知道如何创建您
的对象。
这次,我们将特别详细讨论如何创建进程内对象,以及使创建过程更高效的方法。也将探
讨类对象(也称为类工厂)以及如何实现它。本周已经没有时间讨论如何实现您的实际对
象了,但是这些内容将排在下周日程的前面。
调用 CoCreateInstance 时发生了什么?
我们已经讨论了,当调用 CoCreateInstance 时,COM 如何搜索注册表,找到 CLSID,这
样它才能找到实现对象的 DLL(或 EXE)。但是,我们没有详细讨论这是如何发生的,
CoCreateInstance 封装了下列功能:
IClassFactory *pCF;
CoGetClassObject(rclsid, dwClsContext, NULL,
IID_IClassFactory, (void **)&pCF);
hresult = pCF->CreateInstance(pUnkOuter, riid, ppvObj)
pCF->Release();
正如我们看到的,一共有三个步骤。第一步骤是通过一个类对象的 IID_IClassFactory
接口得到该类对象。下一步,调用这个类对象的 IClassFactory::CreateInstance。(这
个调用的参数从 CoCreateInstance 调用传递进来。)pUnkOuter 参数用于一个重用方法
程序 aggregation 调用,将在稍后讨论。现在假设它是 NULL。在 *ppvObj 中就有了我
们对象一个实例的指针。最后,释放该类对象。
那么,这个类对象又是什么?为什么麻烦它?
类对象
这个类对象是一个特殊的 COM 对象,它的主要目的是实现 IClassFactory 接口。(您将
经常听到这个对象被引用为“类工厂”,甚至“类工厂对象”,但是更准确地应该引用为
类对象。)
如果您经常使用 Java,可以粗略地将 COM 类对象看成一个 "Class" 类的 Java 对象。
而且您将发现 Java 的 Class.newInstance 类似于 IClassFactory::CreateInstance,
COM 的 CoGetClassObject 类似于 Class.forName 静态方法程序。
这个对象是特殊的,因为与大多数 COM 对象不同,它不是通过调用 CoCreateInstance
或 IClassFactory::CreateInstance 创建的。相反,它总是通过调用 CoGetClassObject
创建的。在本篇文章的末尾,我们将看到特殊 COM 对象的其他实例。(正如所证明的,
CoGetClassObject 并不总是创建一个类对象。如果 COM 有正确类的一个类对象,它可以
只返回该类对象的一个接口指针。)
在调用 CoGetClassObject 之后,代码不必关心它创建了哪种对象,例如不管该对象是一
个进程内服务程序还是本地服务程序。类对象管理所有这些差别。为了知道如何创建和查
找 CLSID 所需的一个类对象,CoGetClassObject 不需要查找注册表(以及已有的注册了
的类对象的列表)。
类对象是多态性的强大的一个良好实例。为了得到该对象,我们调用一个 COM API。但是
,当得到该对象之后,我们可以断定它支持所需的标准接口 (IClassFactory),然后可以
调用该接口的方法程序,这里是 IClassFactory::CreateInstance。注意,我们还不知道
类对象的 CreateInstance 是如何工作的。所知道的一切是,如果它成功了,它会返回一
个指向该对象的接口指针。我们不必也不想知道别的了(即封装内容),而且通过进行相
同的函数调用(即多态性),可以得到特定类对象的正确行为—正是类对象的一致性确定
了正确的行为。
每个类对象实例都与一个特定的 CLSID 相关—注意,IClassFactory::CreateInstance
不需要一个 CLSID 作为它的参数。相反,类对象知道要创建什么 CLSID。这意味着,对
于您想要创建的每个单独的 CLSID,至少需要一个类对象。
除了 IclassFactory,类对象可以实现任何接口。例如,您可以定义一个接口,该接口允
许为从特定类对象创建的对象实例设置默认行为。但是,要注意,并不能保证对于一个给
定的 CLSID,只有一个类对象,所以如果您多次调用 CoGetClassObject,可能会得到指
向不同类对象的接口指针。(由于您可以控制类对象的创建,可以在实现过程中定义这一
点。)
为什么要有一个类对象
正如我们讨论过的,COM 需要实现一个类对象的最重要原因是,这样 COM 可以具有创建
任何种类对象的标准多态方法,而客户程序不必知道创建过程的确切细节。类对象封装了
这些内容,这样客户程序就不必知道。这暗示着,类对象和“真正的”对象具有非常紧密
的关系—并且经常互相很了解。
但是,为什么不采用一个更简单的方案呢?例如,您可以想象在您的 COM DLL 中有一个
函数,就叫 DLLCreateInstance 吧,它可以接受一个 CLSID,并且创建一个新实例。一
个函数,比一个 COM 对象和 IclassFactory 要简单地多吧。
但是,它无法为 EXE 工作。您不能从 EXE 导出函数。而且,该函数也肯定不能很好地为
远程对象工作。所以,当我们让类对象作为一个 COM 对象时,COM 会照顾所有的进程内
和线外问题。这是个好买卖。
由于类对象是一个知道如何正确创建目标对象的实例的 COM 对象,注意,一旦创建了类
对象,对于创建实例来说,COM 就不相关了。所以,对于所创建的特定类型的第一个对象
,COM 必须做大量工作。首先,它需要在注册类对象列表中查找 CLSID(或者,如果不存
在类对象,就在注册表中查找)。如果需要创建类对象,COM 会创建它,可能包括加载一
个 DLL 或启动一个 EXE。最后,COM 调用正确类对象的 IClassFactory::CreateInstanc
e 来创建您的实例。哇塞!
但是,如果您保留该类对象,对于后续实例,您可以越过大多数工作:为了创建其他对象
,只需自己调用 IClassFactory::CreateInstance。这几乎与直接接通接线员一样快,而
比让 COM 创建新对象要快得多。
重要内容 如果您保留一个类对象,则必须调用 IClassFactory::LockServer,以便告诉
COM 将服务程序保留在内存中。对类对象的引用不会自动将服务程序保留在内存中。这个
行为是 COM 常规行为的例外。如果您不能锁定服务程序,在服务程序卸载之后,试图访
问类对象就有可能造成一个保护性错误。当您处理完类对象之后,不要忘记解锁该服务程
序。
最后,类对象支持创建对象的其他方法,例如使用 IClassFactory2 接口,而不使用
IClassFactory 创建许可控件。许可控件是在创建该控件之前需要用户拥有正确许可 ID
的控件。
创建对象的另一种方法,以及何时使用该方法
如果您只创建一个对象的一个实例,并且可以使用 IClassFactory 创建该对象,您也可
以用 CoCreateInstance(或 CoCreateInstanceEx,它可以创建远程对象)。但是,如果
您创建一个对象的多个实例,或者如果为了创建该对象,除了 IClassFactory,还需要使
用一个接口,那么您需要获得(可能还需要保留)一个类对象。
获得类对象很容易—就象 CoCreateInstance 做的那样:调用 CoGetClassObject。一旦
您拥有指向类对象的接口指针,需要调用 IClassFactory::LockServer(TRUE) 将服务程
序锁定到内存中。然后,您可以让接口指针继续指向类对象,并且每当需要一个新实例时
都调用 IClassFactory::CreateInstance。最后,当您创建完对象后,需要通过调用
IClassFactory::LockServer(FALSE) 释放服务程序,并且通过调用 Release 释放接口指
针。记住,释放接口必须是操作接口的最后一件事。
实现类对象
那么,这个类对象是什么样呢?它只是一个简单的 COM 对象。这意味着,它至少实现一
个接口:IUnknown。几乎所有的类对象也实现 IclassFactory,所以它们可以创建实例。
我们可能有一个如下声明的类对象:
class CMyClassObject : public IClassFactory
{
protected:
ULONG m_cRef;
public:
CMyClassObject() : m_cRef(0) { };
//IUnknown members
STDMETHODIMP QueryInterface(REFIID, void **);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
//IClassFactory members
STDMETHODIMP CreateInstance(IUnknown *, REFIID iid, void **ppv);
STDMETHODIMP LockServer(BOOL);
};
当然,这个类包含 IclassFactory::CreateInstance 和 LockServer 中每个函数的声明
。还有(奇怪!)IUnknown 函数。(记住,IClassFactory 是从 IUnknown 演化来的,
象所有的 COM 接口一样。)注意,有一个成员保存了该对象的引用计数,并且我们在构
造器中将它初始化为零。也要注意,我们使用正式的 COM 宏来声明方法程序的实现。
这个类对象是如何创建的
有各种方法创建一个类对象,没有一种涉及 CoCreateInstance。由于我们实际只需要这
个对象的一个实例,而且由于是一个没有构造器的小对象,我决定在代码中只声明一个全
局对象:
CMyClassObject g_cfMyClassObject;
这意味着,当加载了 DLL 之后,该对象将一直存在。
为了实现 IClassFactory::LockServer,也需要所有非类对象实例和 LockServer 调用次
数的全局计数:
LONG g_cObjectsAndLocks = 0;
CoGetClassObject 如何获得类对象
对于进程内 DLL 服务程序,就简单了:COM 调用一个在您的 DLL 中名为
DllGetClassObject 的函数。如果您的 DLL 中包含 COM 能创建的 COM 对象,必须导出
该函数。DllGetClassObject 具有下列原型:
STDAPI DllGetClassObject(const CLSID &rclsid, const IID &riid,
void ** ppv);
COM 传递进一个 CLSID 和一个 IID;DllGetClassObject 在 *ppv 中返回一个指向所需
接口的指针。如果不能创建该类对象,或者所需的接口不存在,会在 HRESULT 返回值中
返回一个错误(注意,STDAPI 被定义 (#defined)用来返回一个 HRESULT)。
对于 EXE 服务程序,这个过程就不同了:您为每个 COM 能创建的类注册一个类对象,还
需要为每种类对象调用 CoRegisterClassObject。这将类对象放在注册类对象列表中。当
EXE 过程结束时,对于每个类对象,它调用一次 CoRevokeClassObject,以便将该对象
从注册列表中删除。如果想要详细了解,请参阅 COM 文档或各种 COM 图书。在这里,我
想集中讨论进程内 (DLL) 服务程序。
--
路漫漫兮,其修远。
吾将上下而求索。
※ 修改:.haojs 于 Sep 3 08:02:19 修改本文.[FROM: bbs.hit.edu.cn]
--
※ 转寄:.武汉白云黄鹤站 bbs.whnet.edu.cn.[FROM: bbs.hit.edu.cn]
--
☆ 来源:.哈工大紫丁香 bbs.hit.edu.cn.[FROM: haojs.bbs@bbs.whnet.]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:6.462毫秒