問題
下面代碼產生錯誤的原因是什麼?class CFoolish
{
public:
CFoolish();
~CFoolish();
int m_count;
…
}
int main()
{
CFoolish::m_count = 100;
return 0;
}
答案實在太明顯了—— 沒有創建對象就要訪問類對象的非靜態成員,不出錯才怪!
我們辛苦地創建完一個接口,並按照標準COM的要求實現了它,使客戶程式可以通過QueryInterface方法來得到對象中的接口。但是,有一個問題尚未解決,那就是,我們沒有看到一行可以令COM對象在記憶體中實例化的代碼,因此,如果此時使用這個接口,就會犯與前面所提的簡單問題中同種類型的錯誤。並且,需要提醒大家的是,由於COM對象的複雜性(與前面所講的引用計數一樣),COM實例的創建仍然要依賴內部機制來管理,而不能像普通語言對象一樣,由客戶程式使用簡單的new、delete來控制。
類工廠作用
在COM中,客戶程式必須通過類工廠(Class Factory)來完成創建COM對象的任務。注意:
類工廠也被稱為COM Class Object,可能這個叫法對理解類工廠的用途更有幫助。
為了透徹了解類工廠的運作機制,讓我們看一看類工廠的定義:
class IClassFactory : public IUnknown
{
virtual HRESULT CreateInstance(LPUNKNOWN pUnk ,
REFIID riid, void ** ppv) = 0
virtual HRESULT LockServer(BOOL fLock) = 0;
}
首先可以得出的結論是,類工廠的實質是一個COM對象,它定義了一個IClassFactory接口,正是這個接口中的CreateInstance成員函式,對實例化COM組件起到了核心作用。
另外,根據COM規範,COM Class和類工廠是配對出現的。也就是說,只要有一個實現某一個或某幾個接口的類被編寫出來,若客戶程式想對其進行實例化,就必須相應地實現與這個COM Class配對的類工廠。更具體地說,就是要實現IClassFactory接口。
舉個實際的例子,也許會讓大家更好理解—— 如果我們想要使用我們已經編寫的一些類作一些事情,就必須實現類工廠。反之,客戶程式將無法訪問到類的接口,更不要談調用其成員函式了。
實現
現在,我們看一下應當如何為COM對象實現類工廠。首先,進行類工廠的聲明,代碼如下:
class CSimpleMath_ClassFactory : public IClassFactory
// 聲明用於實現IClassFactory接口的對象
{
protected:
// 用於引用計數的成員變數
long m_RefCount;
public:
CSimpleMath_MathClassFactory();
~ CSimpleMath_MathClassFactory();
// 由於IClassFactory接口繼承自IUnknown接口
// 所以,也繼承了IUnknown接口的三個方法
HRESULT QueryInterface(REFIID, void** );
ULONG AddRef();
ULONG Release();
// IClassFactory接口本身需要實現的方法
HRESULT CreateInstance(LPUNKNOWN, REFIID, void**);
HRESULT LockServer(BOOL bLock);
};
然後,具體實現的代碼如下:
CSimpleMath_ClassFactory:: CSimpleMath_ClassFactory ()
{
// 對象構造時初始化引用計數為0
m_lRef = 0;
}
CSimpleMath_ClassFactory::~ CSimpleMath_ClassFactory ()
{
}
HRESULT CSimpleMath_ClassFactory::QueryInterface( REFIID riid, void** ppv )
{
*ppv = 0;
// 如果查詢的接口為IUnknown或類工廠接口,則返回此實例
if ( riid == IID_IUnknown || riid == IID_ISimpleMathClassFactory )
*ppv = this;
if ( *ppv )
{
// 如果成功地引用到接口,則增加接口的引用計數
AddRef();
return S_OK;
}
return(E_NOINTERFACE);
}
ULONG CSimpleMath_ClassFactory::AddRef()
{
m_RefCount ++;
return m_RefCount;
}
ULONG CSimpleMath_ClassFactory::Release()
{
m_RefCount --; // 減小引用計數
if (m_RefCount == 0 )
{
// 如果引用計數減少至0,則釋放之
delete this;
return 0;
}
return m_RefCount;
}
// *****************************//
// 用來產生COM對象的重要方法
// *****************************//
HRESULT CSimpleMath_ClassFactory::CreateInstance
( LPUNKNOWN pUnkOuter, REFIID riid, void** ppvObj )
{
CSimple_Math * p; // 聲明指向COM Class的指針
HRESULT hr;
*ppvObj = 0;
// 創建COM Class的實例
p = new CSimple_Math;
// 如果沒有成功地創建對象,則返回錯誤值
if (!p)
return(E_OUTOFMEMORY)
// 如果得到了對象,則可以調用QueryInterface方法
hr = p->QueryInterface( riid, ppvObj );
// 如果調用失敗則做好善後工作,並將最終的狀態返回
if ( FAILED( hr ) )
delete p;
return hr;
}
HRESULT CSimpleMath_ClassFactory::LockServer( BOOL bLock )
{
if ( bLock )
{
// 注意:g_LockedCount是COM對象中聲明的一個全局計數變數
// 為了保障執行緒安全,需要對全局變數進行保護
InterlockedIncrement(g_LockedCount) ;
}
else
{
// 注意:g_LockedCount是COM對象中聲明的一個全局計數變數
// 為了保障執行緒安全,需要對全局變數進行保護
InterlockedDecrement(g_LockedCount);
}
return S_OK;
}
這樣就完成了類工廠的具體實現。在整個實現代碼中,最關鍵的部分使用粗體字進行了標註。經過這樣一番精心實現之後,客戶程式便可以通過實例化類工廠COM對象,進而得到類工廠的IClassFactory接口指針,然後,通過對該接口中核心方法CreateInstance的調用完成COM對象的實例化。剩下的事情不言自喻—— 盡情調用COM對象中的服務吧!