荔园在线

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

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


发信人: 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软件 网络书店