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,因此不需要再自己定义入口函数了。

复制代码
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 ULONG Release() =0;
};
class IDBSrvFactory 
{
    // Interface
    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"
// {30DF3430-0266-11cf-BAA6-00AA003E0EED}
static const GUID CLSID_DBSAMPLE = 
{ 0x30df3430, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
// {30DF3431-0266-11cf-BAA6-00AA003E0EED}
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的定义

// {30DF3430-0266-11cf-BAA6-00AA003E0EED}
static const GUID CLSID_DBSAMPLE =
{ 0x30df3430, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
// {30DF3431-0266-11cf-BAA6-00AA003E0EED}
static const GUID IID_IDBSrvFactory =
{ 0x30df3431, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
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的方式。

复制代码
    //新建数据库对象
    //m_pDB=new CDB;
    IDBSrvFactory *pDBFactory=NULL;
    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);
        return FALSE;
    }
    pDBFactory->CreateDB(&m_pDB);
    pDBFactory->Release(); // do not need the factory anymore
复制代码
注:以前在客户程序中需要对应的DLL在路径下,但现在并不需要了,因为COM库会根据注册表中的信息找到DLL所在路径的。



本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2008/08/29/1279479.html,如需转载请自行联系原作者
目录
相关文章
|
1月前
|
存储 C++
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
24 2
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
|
6月前
|
C++
C++代码的可读性与可维护性:技术探讨与实践
C++代码的可读性与可维护性:技术探讨与实践
108 1
|
1月前
|
存储 编译器 C语言
C++类与对象深度解析(一):从抽象到实践的全面入门指南
C++类与对象深度解析(一):从抽象到实践的全面入门指南
49 8
|
2月前
|
C++
c++继承层次结构实践
这篇文章通过多个示例代码,讲解了C++中继承层次结构的实践应用,包括多态、抽象类引用、基类调用派生类函数,以及基类指针引用派生类对象的情况,并提供了相关的参考链接。
|
5月前
|
关系型数据库 MySQL 测试技术
技术分享:深入C++时间操作函数的应用与实践
技术分享:深入C++时间操作函数的应用与实践
47 1
|
5月前
|
C++
C++解决线性代数矩阵转置 小实践
【6月更文挑战第3天】C++解决线性代数矩阵转置
72 2
|
5月前
|
存储 算法 安全
用C++打造极致高效的框架:技术探索与实践
本文探讨了如何使用C++构建高性能框架。C++凭借其高性能、灵活性和跨平台性成为框架开发的理想选择。关键技术和实践包括:内存管理优化(如智能指针和自定义内存池)、并发编程(利用C++的并发工具)、模板与泛型编程以提高代码复用性,以及性能分析和优化。在实践中,应注意代码简洁性、遵循最佳实践、错误处理和充分测试。随着技术发展,不断提升对框架性能的要求,持续学习是提升C++框架开发能力的关键。
115 1
|
5月前
|
C++
C++程序设计实践一上(题目来自杭州电子科技大学ACM)
C++程序设计实践一上(题目来自杭州电子科技大学ACM)
38 2
|
5月前
|
C++
C++程序设计实践一下(题目来自杭州电子科技大学ACM)
C++程序设计实践一下(题目来自杭州电子科技大学ACM)
45 1
|
6月前
|
安全 算法 程序员
探索C++的魅力:语言特性、编程实践及代码示例
C++是广泛应用的编程语言,尤其在系统级编程、应用开发、游戏和嵌入式系统中广泛使用。其主要特性包括:面向对象编程(封装、继承、多态),泛型编程(通过模板实现代码复用和类型安全),以及丰富的标准库和第三方库。在编程实践中,需注意内存管理、异常处理和性能优化。示例代码展示了面向对象和泛型编程,如类的继承和泛型函数的使用。C++的内存管理和库支持使其在解决复杂问题时具有高效和灵活性。