Programming 版 (精华区)

发信人: lofe ()感激生活(), 信区: Programming
标  题: 组件、COM和ATL(6)
发信站: 哈工大紫丁香 (Sun Sep  3 08:05:15 2000), 转信

发信人: kywu (太阳星辰), 信区: VisualC
标  题: 组件、COM 和 ATL(6)
发信站: BBS 水木清华站 (Mon Mar 22 13:18:40 1999) WWW-POST

注意,当您调用 CoGetClassObject 时,COM 如何真正获得类对象取决于该对象是由一个
 DLL 还是一个 EXE 实现的。如果是由 DLL 实现的,COM 加载这个 DLL(如果还没有加
载),并且调用 DllGetClassObject。对于一个 EXE,COM 加载这个 EXE(如果还没有加
载),并且等到 EXE 注册它寻找的类对象,或者等到发生超时。

我们的 DllGetClassObject 可能会象这样:

STDAPI DllGetClassObject(REFCLSID clsid, REFIID iid, void **ppv) {
   if (clsid != CLSID_MyObject) // Right CLSID?
      return CLASS_E_CLASSNOTAVAILABLE;

   // 从全局对象获得接口。
   HRESULT hr = g_cfMyClassObject.QueryInterface(iid, ppv);
   if (FAILED(hr))
      *ppv = NULL;
   return hr;
}
我们必须检查,看所需的 CLSID 是否是我们支持的。如果不是,就返回 E_FAIL。接下来
,调用所需接口的 QueryInterface。如果失败,就将输出指针设置为 NULL,并返回
E_NOINTERFACE。如果成功,就返回 S_OK 和接口指针。

实现类对象的方法程序
IUnknown::AddRef 和 IUnknown::Release
我们的类对象是全局的。它总是存在,并且不能被破坏(至少是在卸载 DLL 之前)。由
于我们从来也不删除这个对象,而且对类对象的引用不能使一个服务程序加载,所以几乎
不需要实施引用计数。但是,引用计数对调试会有帮助,所以我们还是实现这个对象的引
用计数。

AddRef 和 Release 负责维护对象的引用计数。注意,我们有一个初始化为零的实例变量
 m_cRef。AddRef 和 Release 只是增加和减少这个引用计数器,并且返回引用计数器的
新值。

如果对象是动态创建的,当引用计数为零时,删除该对象是 Release 的责任。由于我们
的对象是全局分布的,所以不能这样做。

STDMETHODIMP_(ULONG) CMyClassObject::AddRef() {
   return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CMyClassObject::Release() {
   return InterlockedDecrement(&m_cRef);
}

我使用有线程保护的增加和减少函数,而不是使用 ++m_cRef 和 --m_cRef,以符合多线
程操作的思维习惯。

如果您想让 AddRef 和 Release 真的简单,只需让它们返回一个非零值,也可以删除类
对象的用于引用计数的成员变量(不是删除该对象,和锁定全局变量的计数!)。

IUnknown::QueryInterface
这个对象的 QueryInterface 的实现是 100% 标准的,没有特殊的内容,因为该对象是一
个类对象。需要我们做的只是看所需的接口是否是我们支持的两种接口之一(IUnknown
和 IclassFactory)。如果是,就在正确地进行类型转换之后,向对象返回一个接口指针
;而且,对于正确的指针,调用 AddRef 以便引用计数。如果不是,就返回正确的错误代
码 E_NOINTERFACE。

STDMETHODIMP CMyClassObject::QueryInterface(REFIID iid, void ** ppv) {
   *ppv = NULL;
   if (iid == IID_IUnknown==iid || iid == IID_IClassFactory) {
      *ppv = static_castthis;
      (static_cast*ppv)->AddRef();
      return S_OK;
   else {
      *ppv = NULL; // 按 COM 规范,如果失败需要为 NULL
      return E_NOINTERFACE;
   }
}
注意那个新的 static_cast 操作符。在 ANSI C++ 中,通过使用不同的操作符,您可以
区分类型转换的三种不同语义使用。static_cast 操作符在指针和不同的类类型之间进行
相应的类型转换,如果必要就更改指针的值(这个不是这种情况,因为我没有使用多重继
承)。

IClassFactory::CreateInstance
这里是我们的类对象的中心—创建实例的函数。

STDMETHODIMP CMyClassObject::CreateInstance (IUnknown *pUnkOuter,
   REFIID iid, void ** ppv)
{
   *ppv=NULL;

// 对集合只需说不。
   if (pUnkOuter != NULL)
      return CLASS_E_NOAGGREGATION;

   //创建该对象。
   CMyObject *pObj = new CMyObject();
   if (pObj == NULL)
      return E_OUTOFMEMORY;

   //获得第一个接口指针(执行了一次 AddRef)。
   HRESULT hr = pObj->QueryInterface(iid, ppv);

   // 如果接口不可用,就删除该对象。
   //假设初始的引用计数为零。
   if (FAILED(hr))
      delete pObj;

   return hr;
}
首先,我们不支持集合。所以如果该指针不是 NULL,则不能创建该对象,因为我们被要
求支持集合。下一步,分配该对象,如果不能分配该对象,就返回 E_OUTOFMEMORY。

接下来,对于新创建的对象,调用 QueryInterface,以得到要返回的接口指针。如果失
败,就删除该对象,并且返回错误代码。如果成功,就从 QueryInterface 返回成功代码
。注意,如果成功,QueryInterface 将调用 AddRef,使我们得到对象的正确引用计数。


也要注意,我们并没有增加对象,也没有锁定计数器 g_cObjectsAndLocks。如果创建成
功了,我们本来可以这样做,但是也必须在实例对象的 Release 或析构器中减少计数器
。我们将把计数器的减少代码放在对象本身的析构器中—在第五部分。但是如果减少代码
是在析构器中,那么增加代码应该放在构造器中,而不是在这里。

有很多不同方式对对象进行初始的 QueryInterface 操作,这取决于对象本身是如何进行
初始引用计数的。随之出现的一个问题是,有时一个对象会在 QueryInterface 过程中进
行某些动作,这些动作会引起 AddRef 和 Release 这对调用的执行。如果对象的初始引
用计数为零,对 Release 的调用会使对象释放自己—甚至在 CreateInstance 返回之前
。这可不太好。

一个常用的技术是,将对象的初始引用计数设置为一个非零的数。可以很容易地在对象的
构造器中做到这一点(请参阅第五部分)。但是,如果您这样做了,就必须修改
CreateInstance,在它调用 QueryInterface 之后调用 Release,这样就会正确设置引用
计数。

如果您这样做了,就忽略了删除该对象。如果 QueryInterface 失败了,它将不调用
AddRef—所以对象的引用计数将会是 1 而不是 2。如果这时调用了 Release,对象的引
用计数将变成零,并且对象将删除自己。如果 QueryInterface 成功了,它将引用计数增
加为 2,然后 Release 将引用计数减少为 1,这时才正确。

如果您假设初始引用计数为 1,可将 QueryInterface 结尾的的 CreateInstance 代码确
定为如下所示:

// ...

//获得第一个接口指针(执行了一次 AddRef)。
   HRESULT hr = pObj->QueryInterface(iid, ppv);

   // 如果接口不可用,就删除该对象。
   // 假设初始的引用计数为 1,而不是零。
    pObj->Release(); // 如果 QI 成功了,就变回 1,如果没成功就删除它

   return hr;
}
我们将在第五部分将这些代码用于我们的对象:它很简单,而且好用。对于
CreateInstance 必须知道对象的实现细节,医生不认为这是一个缺点—毕竟,这就是
CreateInstance 的用处:为了封装这些细节,这样客户程序就不必管它们。

IClassFactory::LockServer
LockServer 只是用来增加和减少全局锁定和对象计数。当计数变成零时,它不会试图释
放 DLL。(如果这是一个 EXE 服务程序,并且没有任何交互用户,则当计数变成零时,
服务程序将关闭。)

STDMETHODIMP CMyClassObject::LockServer(BOOL fLock) {
   if (fLock)
      InterlockedIncrement(&g_cObjectsAndLocks);
   else
      InterlockedDecrement(&g_cObjectsAndLocks);
   return NOERROR;
}

另外,我选择让这些代码是线程保护的。当计数变成零时,可以删除该对象。

DllCanUnloadNow
COM 将调用 DllCanUnloadNow,以决定是否卸载一个 DLL。如果可以卸载,我们只是简单
地返回 S_OK,如果不可以,将返回 S_FALSE。如果没有对象或对服务程序的锁定,就可
以卸载。

STDAPI DllCanUnloadNow() {
   if (g_cObjectsAndLocks == 0)
     return S_OK;
   else
      return S_FALSE;
}

回顾与前瞻
我们部分地讨论了如何创建进程内对象,以及让创建过程更有效的方法。也讨论了类对象
(也称为类工厂),以及如何实现一个类对象。但是,我们没有开始真的实现一个对象。


下一次,我们将讨论实现一个实例对象的本质,包括 IUnknown 所需的代码和您自己的自
定义接口—而且,还有可能讨论特殊的、高效的、 不使用 COM 来创建的 COM 对象。

注意,我们使用了 C++ 来实现,但是也可以使用 C 语言。医生不认为这可取(特别是,
将 C 和 C++ 程序混合就不错了)。但是,如果有什么特殊理由使您真的想这样做,在
MSDN 上有一些示例,包括 Inside OLE 第二章的标题 "RectEnumerator in C:
ENUMC.C,"。

轮到您了
您是否有想让医生讨论的主题?可以给 drgui@microsoft.com 来信。虽然,医生手术计
划不包括个别答复,但是 GUI 医生保证阅读和考虑所有邮件。

--

                路漫漫兮,其修远。
                吾将上下而求索。
※ 修改:.haojs 于 Sep  3 08:02:46 修改本文.[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)
页面执行时间:7.826毫秒