天天看點

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

    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,如需轉載請自行聯系原作者