一,使用抽象基類重用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
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"
class CDB : public IDB
HRESULT Read(short nTable, short nRow, LPWSTR lpszData);
HRESULT Write(short nTable, short nRow, LPCWSTR lpszData);
HRESULT Create(short &nTable, LPCWSTR lpszName);
HRESULT Delete(short nTable);
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
~CDB();
class CDBSrvFactory : public IDBSrvFactory
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,是以不需要再自己定義入口函數了。
// {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 } };
// 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;
public:
virtual HRESULT CreateDB(IDB** ppObject) =0;
virtual ULONG Release() =0;
//HRESULT DEF_EXPORT DllGetClassFactoryObject(IDBSrvFactory ** ppObject);
2,修改對象程式
修改如下:1)為類ID和接口ID定義GUID。2)将DllGetClassFactoryObject改成标準入口函數DllGetClassObject。具體代碼如下:
#include "stdafx.h"
#include "DBsrvImp.h"
static const GUID CLSID_DBSAMPLE =
{ 0x30df3430, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
static const GUID IID_IDBSrvFactory =
{ 0x30df3431, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
// Create a new database object and return a pointer to it
HRESULT CDBSrvFactory::CreateDB(IDB** ppvDBObject)
*ppvDBObject=(IDB*) new CDB;
return NO_ERROR;
ULONG CDBSrvFactory::Release()
delete this;
return 0;
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppObject)
if (rclsid!=CLSID_DBSAMPLE)
{
return CLASS_E_CLASSNOTAVAILABLE;
}
if (riid!=IID_IDBSrvFactory)
return E_INVALIDARG;
*ppObject=(IDBSrvFactory*) new CDBSrvFactory;
return NO_ERROR;
注:1)由于标準入口函數DllGetClassObject在 objbase.h檔案中已經聲明了,是以在我們的代碼中不必再聲明它。2)在stdafx.h中加入了#include <ole2.h>,并且在所有的include前加入了:#define _AFX_NO_BSTR_SUPPORT,這是因為MFC頭檔案中的一些定義和ole2.h中不一樣。
然後,要讓客戶程式通路到入口函數,我們要為建立一個子產品定義DEF檔案,在其中引出DllGetClassObject函數,DB.def代碼如下:
EXPORTS
;WEP @1 RESIDENTNAME
DllGetClassObject
注:不能用__declspec(dllexport)來引出DllGetClassObject函數,因為在objbase.h中它的定義處已經使用了其他修飾詞。
最後,這個對象要想通過COM庫建立,就必須在系統資料庫中進行注冊,但目前還沒有加入自我注冊部分,是以我們先對其進行手動注冊吧。其實要做的事情很簡單,就是把我們的DLL的路徑告訴給COM庫就行了。步驟如下:
1)HKEY_CLASSES_ROOT"CLSID下添加一個子鍵,名字就是上面定義的類ID:{ 30DF3430-0266-11cf-BAA6-00AA003E0EED}。
2)為這個子鍵再添加一個子鍵InprocServer32,為它添加一個未命名的字元串值:類型為REG_SZ,資料為<path>"db.dll,也就是儲存你的DLL的路徑。
3,修改客戶程式
1)在DBDoc.cpp中也加入類ID和接口ID的定義
static const GUID CLSID_DBSAMPLE =
static const GUID IID_IDBSrvFactory =
2)初始化COM庫。在我們使用COM庫之前先對其進行初始化。
if (FAILED(CoInitialize(NULL)))
AfxMessageBox(_T("Could not initialize COM Libraries!"));
return FALSE;
并且在程式退出時調用CoUninitialize()
int CDBApp::ExitInstance()
CoUninitialize();
return CWinApp::ExitInstance();
2)改為使用COM庫函數來建立對象。在CDBDoc::OnNewDocument()函數中,使用COM庫函數CoGetClassObject代替原來直接裝載DLL的方式。
HRESULT hRes;
hRes=CoGetClassObject(CLSID_DBSAMPLE, CLSCTX_SERVER, NULL, IID_IDBSrvFactory, (void**) &pDBFactory);
if (FAILED(hRes))
CString csError;
csError.Format(_T("Error %x creating DB Object!"), hRes);
AfxMessageBox(csError);
注:以前在客戶程式中需要對應的DLL在路徑下,但現在并不需要了,因為COM庫會根據系統資料庫中的資訊找到DLL所在路徑的。
本文轉自Phinecos(洞庭散人)部落格園部落格,原文連結:http://www.cnblogs.com/phinecos/archive/2008/08/29/1279479.html,如需轉載請自行聯系原作者