第4章
本章實現了組件IUnknown接口中的AddRef()和Release()函數。這兩個函數實現很簡單,本章主要是介紹了客戶端程序在什麼情況下調用組件的AddRef和Release函數。
客戶程序通過這兩個函數實現了對組件生命期(創建,釋放)的控制。通過控制接口的生命期來控制組件生命期,因爲客戶能夠知道何時開始使用一個接口(AddRef(),引用計數加一),何時使用完這個接口(Release(),引用計數減一),這樣當客戶使用完組件的所有接口後,組件會完成對自己的釋放。
事實上在後面的章節,會介紹智能指針,不要客戶去AddRef和Release(實際上,智能指針的內部實現也是使用引用計數的方法)
COM組件中(實現COM的C++類中)有一個稱作是引用計數的值。當客戶從組件中獲得一個接口時,該引用計數增1,當客戶使用完成某個接口後,組件的引用計數減1,當引用計數爲0時,組件便將自己從內存中刪除。正確的使用引用計數,有三條規則
1.返回接口之前調用AddRef。目前包括QueryInterface和CreateInstance,這樣在客戶端只需用完接口進行Release就好了。
2.客戶使用接口完成後,調用該接口的Release。
3.在將一個接口指針賦值給另外一個接口時,調用被賦值接口的AddRef。
對於第3條,用之前的IX接口舉例如下:
IX *pIx2 = pIx;
pIx2->AddRef();
實際上爲每一個接口維護一個引用計數,可以方便程序的調試和資源的釋放(可以快速定位到某個接口的計數,進而只查找該接口的問題,或者釋放在該接口上的資源。感覺這種計數方式合理很多,書上暫時沒用這種方式,以後研究下,書上使用的方法是對一個COM組件維護一個使用計數)。
在客戶看來,爲每一個接口維護一個引用計數和整個組件維護單個引用計數沒有差別,都是調用AddRef和Release。作者爲了組件實現起來簡單,爲整個組件維護了單個的引用計數。
AddRef和Release的實現
ULONG __stdcall AddRef()
{
return InterlockedIncrement(&m_cRef);
}
ULONG __stdcall Release()
{
if(InterlockedDecrement(&m_cRef) == 0)
{
delete this;
return 0;
}
return m_cRef
}
考慮多線程的關係,使用了Win32的API函數InterlockedIncrement和InterlockedDecrement,實現了對m_cRef自加和自減的原子操作。AddRef和Release的返回值只有調試程序才用得上。
關於引用計數還有幾條規則需要注意
1.輸出參數規則。輸出接口在返回值前必須調用AddRef
2.輸入參數規則。輸入接口參數的生命期永遠在調用函數的生命期內,所以不需要AddRef,更不需要Release。
3.輸入輸出參數規則。接口在被賦值之前,先Release,然後再AddRef。
4.局部變量。等同於輸入參數規則。
5.全局變量和不能確定時,都需要調用AddRef和Release。
本章的例子:
//IUnkown.cpp
//use:cl IUnkown.cpp UUID.lib
//
#include <iostream>
#include <string>
#include <objbase.h>
using namespace std;
void trace(string msg)
{
cout<<msg<<endl;
}
//Interfaces
interface IX:IUnknown
{
virtual void __stdcall Fx() = 0;
};
interface IY:IUnknown
{
virtual void __stdcall Fy() = 0;
};
interface IZ:IUnknown
{
virtual void __stdcall Fz() = 0;
};
extern const IID IID_IX ;
extern const IID IID_IY ;
extern const IID IID_IZ ;
//Component
class CA:public IX, public IY
{
virtual HRESULT __stdcall QueryInterface(const IID &iid, void **ppv);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
virtual void __stdcall Fx()
{
cout<<"Fx"<<endl;
}
virtual void __stdcall Fy()
{
cout<<"Fy"<<endl;
}
public:
CA():m_cRef(0) {}
~CA() {trace("CA:Destroy itselt");}
private:
long m_cRef;
};
HRESULT __stdcall CA::QueryInterface(const IID &iid, void **ppv)
{
if(iid == IID_IUnknown)
{
trace("Return pointer to IUnkown");
*ppv = static_cast<IX*>(this);
}
else if(iid == IID_IX)
{
trace("pointer to IX");
*ppv = static_cast<IX*>(this);
}
else if(iid == IID_IY)
{
trace("return pointer to IY");
*ppv = static_cast<IY*>(this);
}
else
{
trace("Interface not supported");
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
ULONG __stdcall CA::AddRef()
{
cout<<"CA:AddRef "<<m_cRef + 1<<endl;
return InterlockedIncrement(&m_cRef);
}
ULONG __stdcall CA::Release()
{
cout<<"CA:Release "<<m_cRef - 1<<endl;
if(InterlockedDecrement(&m_cRef) == 0)
{
delete this;
return 0;
}
return m_cRef;
}
//Create function
IUnknown *CreateInstance()
{
IUnknown *pI = static_cast<IX*>(new CA);
pI->AddRef();
return pI;
}
// IIDs
// {32bb8320-b41b-11cf-a6bb-0080c7b2d682}
static const IID IID_IX = {0x32bb8320, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ;
// {32bb8321-b41b-11cf-a6bb-0080c7b2d682}
static const IID IID_IY = {0x32bb8321, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ;
// {32bb8322-b41b-11cf-a6bb-0080c7b2d682}
static const IID IID_IZ = {0x32bb8322, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ;
///////////////////////////////////////////////////////
//Client
///////////////////////////////////////////////////////
int main(void)
{
HRESULT hr;
trace("Client: Get Iunkown pointer");
IUnknown *pIunknown = CreateInstance();
trace("Client: Get interface IX");
IX *pIx = NULL;
hr = pIunknown->QueryInterface(IID_IX, (void**)&pIx);
if(SUCCEEDED(hr))
{
trace("Client:Succeeded get IX");
pIx->Fx();
pIx->Release();
}
trace("Client: Get interface IY");
IY *pIy = NULL;
hr = pIunknown->QueryInterface(IID_IY, (void**)&pIy);
if(SUCCEEDED(hr))
{
trace("Client: Succeeded get IY");
pIy->Fy();
pIy->Release();
}
trace("Client: Ask for an unsupported interface");
IZ *pIz = NULL;
hr = pIunknown->QueryInterface(IID_IZ, (void**)&pIz);
if(SUCCEEDED(hr))
{
trace("Client: Succeeded get IZ");
pIz->Fz();
pIz->Release();
}
else
{
trace("Client: Could not get interface IZ");
}
pIunknown->Release();
return 0;
}
運行結果