荔园在线
荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀
[回到开始]
[上一篇][下一篇]
发信人: Deny (中秋快乐!!!!!!!!!!!!!!!), 信区: Program
标 题: 组件、COM和ATL(6)
发信站: 荔园晨风BBS站 (Wed Sep 26 22:13:31 2001), 转信
COM专题---组件、COM和ATL(6)
对象的引用计数
当您调用 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_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,对象的引 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,"。
--
※ 修改:·Deny 於 Sep 28 13:47:01 修改本文·[FROM: 192.168.1.39]
※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.1.201]
[回到开始]
[上一篇][下一篇]
荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店