COM組件開發實踐(五)---From C++ to COM :Part 2
一,使用抽象基類重用C++對象
在上一篇文章《COM組件開發實踐(四)---From C++ to COM :Part 1》中,我們已經將要複用的C++對象封裝到DLL中了,對象的聲明和實現已經實現了剝離,但還有問題:對象的私人成員(如我們樣本中CDB類的陣列變數m_arrTables)還是被客戶看得一清二楚,即使客戶沒辦法去訪問它們;若對象改變了它的資料成員的大小,則所有的客戶程式必須重新編譯。
而實際上,客戶需要的僅僅是對象的成員函數的地址,因此使用抽象基類可以很好地滿足以上需求,客戶就不會包含對象的私人資料成員,就算對象改變了資料成員的大小,客戶程式也不用重新編譯。
1. 修改介面檔案
首先將介面都改成抽象基類,這是客戶程式唯一所需要的代碼。具體包括下面幾步:1)將CDB和CDBSrvFactory的函數都改成純虛函數。2)刪除資料成員。3)刪除所有成員函數的引出標誌。4)將CDB改成IDB(表示DB的介面),CDBSrvFactory改成IDBSrvFactory(表示DB類工廠的介面) typedef long HRESULT;
#define DEF_EXPORT __declspec(dllexport)
class IDB
{
// Interfaces
public :
// Interface for data access
virtual HRESULT Read( short nTable, short nRow, LPWSTR lpszData) = 0 ;
virtual HRESULT Write( short nTable, short nRow, LPCWSTR lpszData) = 0 ;
// Interface for database management
virtual HRESULT Create( short & nTable, LPCWSTR lpszName) = 0 ;
virtual HRESULT Delete( short nTable) = 0 ;
// Interfase para obtenber informacion sobre la base de datos
virtual HRESULT GetNumTables( short & nNumTables) = 0 ;
virtual HRESULT GetTableName( short nTable, LPWSTR lpszName) = 0 ;
virtual HRESULT GetNumRows( short nTable, short & nRows) = 0 ;
virtual ULONG Release() = 0 ;
};
class IDBSrvFactory
{
// Interface
public :
virtual HRESULT CreateDB(IDB ** ppObject) = 0 ;
virtual ULONG Release() = 0 ;
};
HRESULT DEF_EXPORT DllGetClassFactoryObject(IDBSrvFactory ** ppObject);
2.修改對象程式
在DLL項目中,我們實現為這個抽象介面聲明並實現具體的子類,讓CDB從IDB派生,CDBSrvFactory從IDBSrvFactory派生,並且把類工廠CDBSrvFactory的CreateDB方法的參數由CDB**改成IDB**。 #include " ../interface/dbsrv.h "
typedef long HRESULT;
class CDB : public IDB
{
// Interfaces
public :
// Interface for data access
HRESULT Read( short nTable, short nRow, LPWSTR lpszData);
HRESULT Write( short nTable, short nRow, LPCWSTR lpszData);
// Interface for database management
HRESULT Create( short & nTable, LPCWSTR lpszName);
HRESULT Delete( short nTable);
// Interfase para obtenber informacion sobre la base de datos
HRESULT GetNumTables( short & nNumTables);
HRESULT GetTableName( short nTable, LPWSTR lpszName);
HRESULT GetNumRows( short nTable, short & nRows);
ULONG Release(); // CPPTOCOM: need to free an object in the DLL, since it was allocated here
// Implementation
private :
CPtrArray m_arrTables; // Array of pointers to CStringArray (the "database")
CStringArray m_arrNames; // Array of table names
public :
~ CDB();
};
class CDBSrvFactory : public IDBSrvFactory
{
// Interface
public :
HRESULT CreateDB(IDB ** ppObject);
ULONG Release();
};
這兩個具體子類的實現代碼在此就省略不表了,參考上篇文章。
3.修改客戶程式
最後根據上面的修改,對客戶程式也做相應修改:1)將CDBDoc類的資料成員類型由CDB*改成IDB*。 IDB * m_pDB; // pointer to database object
2)CDBDoc::OnNewDocument函數中,將CDBSrvFactory*改成IDBSrvFactory* BOOL CDBDoc::OnNewDocument()
{
…
// 建立資料庫物件
// m_pDB=new CDB;
IDBSrvFactory * pDBFactory = NULL;
DllGetClassFactoryObject( & pDBFactory);
pDBFactory -> CreateDB( & m_pDB);
pDBFactory -> Release(); // do not need the factory anymore
// 初始化資料成員變數
…
return TRUE;
}
OK,最後重新編譯DLL即可。可以看出,通過使用虛函數和抽象基類,這才算真正實現了面向介面編程。
二,初步逼近COM--使用COM庫載入C++對象
上面的代碼中聲明了DLL的一個進入點DllGetClassFactoryObject,而客戶程式就是通過調用這個函數,從而擷取到相應的類工廠對象,再使用類工廠對象建立真正的對象的。我們這一步要嘗試讓客戶程式不去直接調用對象的入口函數,而是讓COM庫為我們服務,並且要讓對象使用標準的入口函數。
1,修改介面定義檔案
首先在介面定義檔案中增加類ID和介面ID的聲明,這兩個ID在對象程式和客戶程式中都會有定義,在這裡只是對這兩個外部變數進行說明。然後將引出函數DllGetClassFactoryObject刪除,因為接下來我們將會使用標準入口函數DllGetObject,因此不需要再自己定義入口函數了。2,修改對象程式 typedef long HRESULT;
#define DEF_EXPORT __declspec(dllexport)
// {30DF3430-0266-11cf-BAA6-00AA003E0EED}
extern const GUID CLSID_DBSAMPLE;
// { 0x30df3430, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
// {30DF3431-0266-11cf-BAA6-00AA003E0EED}
extern const GUID IID_IDBSrvFactory;
// { 0x30df3431, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
class IDB
{
// Interfaces
public :
// Interface for data access
virtual HRESULT Read( short nTable, short nRow, LPWSTR lpszData) = 0 ;
virtual HRESULT Write( short nTable, short nRow, LPCWSTR lpszData) = 0 ;
// Interface for database management
virtual HRESULT Create( short & nTable, LPCWSTR lpszName) = 0 ;
virtual HRESULT Delete( short nTable) = 0 ;
// Interfase para obtenber informacion sobre la base de datos
virtual HRESULT GetNumTables( short & nNumTables) = 0 ;
virtual HRESULT GetTableName( short nTable, LPWSTR lpszName) = 0 ;
virtual HRESULT GetNumRows( short nTable, short & nRows) = 0 ;
virtual