COM组件开发实践(四)---From C++ to COM :Part 1

简介:
源代码下载 – 74kb

一,C++客户重用C++对象

      假设已经有一个可以重用的类,我们就可以在自己的程序中去重用它,只需要将其定义和实现文件加入到我们自己的工程中,并且在使用它的文件中包含此类的定义文件就可以了,这也是我们最常用的C++标准重用方法。就拿我自己来说,在CodeProject上遇到比较好的控件代码,都是这样直接用到自己的项目中来的。

      下面就给出我这个系列的第一个代码示例,在接下来的几篇文章中,将基于此代码不断进行改进,一步步从C++走向COM.

      简单介绍下我们要重用的C++对象,它是一个简单的类似数据库的对象,用来管理内存中的数据,它包含一个指向“数据库”中所有表的指针数组,表实际是一个字符串数组,每个数组元素表示表格的一行。另外这个类还包含有一个数据表表名的数组。

DBSRV.h文件:

复制代码
typedef long HRESULT;//模拟COM中的HRESULT

//内存数据库类
class CDB 
{
  // Interfaces
  public:
          // Interface for data access
        HRESULT Read(short nTable, short nRow, LPTSTR lpszData);//读数据,nTable指定数据表,nRow指定数据行
        HRESULT Write(short nTable, short nRow, LPCTSTR lpszData);//写数据,nTable指定数据表,nRow指定数据行
        // Interface for database management
        HRESULT Create(short &nTable, LPCTSTR lpszName);//创建数据表,表名为lpszName
        HRESULT Delete(short nTable);//删除数据表
        // Interface for database information
        HRESULT GetNumTables(short &nNumTables);//获取数据表个数
        HRESULT GetTableName(short nTable, LPTSTR lpszName);//获取指定数据表表名,nTable为数据表索引号
        HRESULT GetNumRows(short nTable, short &nRows);//获取指定数据表的数据行数,nTable为数据表索引号,nRows保存返回的行数
  // Implementation
  private:
      CPtrArray      m_arrTables;      //指向“数据库”中所有表的指针数组
    CStringArray m_arrNames; //数据表名称数组
    public:
        ~CDB();
};
复制代码
DBSRV.cpp文件:

复制代码
#include "stdafx.h"
#include "..\Interface\DBsrv.h"
// Database object
HRESULT CDB::Read(short nTable, short nRow, LPTSTR lpszData) 
{
  CStringArray *pTable;
  pTable=(CStringArray*) m_arrTables[nTable];
  lstrcpy (lpszData, (*pTable)[nRow]);
  return NO_ERROR;
}

HRESULT CDB::Write(short nTable, short nRow, LPCTSTR lpszData) 
{
  CStringArray *pTable;
  pTable=(CStringArray*) m_arrTables[nTable];
  pTable->SetAtGrow(nRow, lpszData);
  return NO_ERROR;
}
HRESULT CDB::Create(short &nTable, LPCTSTR lpszName) 
{
  CStringArray *pTable=new CStringArray;
  nTable=m_arrTables.Add(pTable);
  m_arrNames.SetAtGrow(nTable, lpszName);
  return NO_ERROR;
}
HRESULT CDB::Delete(short nTable) 
{
  CStringArray *pTable;
  pTable=(CStringArray*) m_arrTables[nTable];
  delete pTable;
  m_arrTables[nTable]=NULL;
  m_arrNames[nTable]="";
  if (nTable==m_arrTables.GetSize()-1) 
  {
        m_arrTables.RemoveAt(nTable);
        m_arrNames.RemoveAt(nTable);
    }
  return NO_ERROR;
}
HRESULT CDB::GetNumTables(short &nNumTables) 
{
  nNumTables=m_arrTables.GetSize();
    return NOERROR;
}

HRESULT CDB::GetTableName(short nTable, LPTSTR lpszName) 
{
  lstrcpy(lpszName, m_arrNames[nTable]);
  return NO_ERROR;
}
HRESULT CDB::GetNumRows(short nTable, short &nRows)
{
  CStringArray *pTable;
  pTable=(CStringArray*) m_arrTables[nTable];
  return pTable->GetSize();
}
CDB::~CDB() 
{
  short nNumTables;
  for (GetNumTables(nNumTables);nNumTables>0; GetNumTables(nNumTables)) 
  {
        Delete(nNumTables-1);
  }
}
复制代码
     客户程序是一个简单的MFC单文档程序,为程序添加三个菜单项“建表”,“写表”,“读表”,对应的处理函数在CDBDoc中实现。

复制代码
public:
    CDB *m_pDB; // pointer to database object
    CString m_csData; // last data read from database
    int m_nCount;            // number of writes to database
    short m_nTable;        // number of last table created
CDBDoc::CDBDoc()
{
    m_pDB=NULL;
}
CDBDoc::~CDBDoc()
{
    if (m_pDB) 
    {
        delete m_pDB; //释放对象
        m_pDB=NULL;
    }
}
BOOL CDBDoc::OnNewDocument()
{
    if (!CDocument::OnNewDocument())
        return FALSE;
    //新建数据库对象
    m_pDB=new CDB;
    // 初始化数据成员变量
    m_csData="No data yet!"; 
    m_nCount=0;        
    m_nTable=-1;    
    return TRUE;
}
// 菜单项处理函数区
void CDBDoc::OnCreateTable() 
{// 建表
    m_pDB->Create(m_nTable, _T("Testing"));    
    m_nCount=0; // set number of writes to 0 
}
void CDBDoc::OnReadTable() 
{// 读表
    m_pDB->Read(m_nTable, 0, m_csData.GetBuffer(80));
    m_csData.ReleaseBuffer();
    UpdateAllViews(NULL);
}
void CDBDoc::OnWriteTable() 
{// 写表
    m_nCount++;
    CString csText;
    csText.Format(_T("Test data #%d in table %d, row 0!"), m_nCount, (int) m_nTable);
    m_pDB->Write(m_nTable, 0, csText);
}
复制代码
最后在CDBView的OnDraw函数中添加如下语句来显示读表读取到的内容:

pDC->TextOut(10,10, pDoc->m_csData); 
二,将C++对象打包到DLL中

      第一节中的标准重用方法有一个大毛病:类的实现代码被泄露了,而这想必不是我们想要的结果。要解决这个问题,我们可以使用DLL将类的代码打包成一个DLL,并提供一个用于说明函数和结构的头文件,这样实现代码就封装起来了。基于上一节的代码,我们修改如下:

一)先修改接口文件:1)为每个成员函数添加_declspec(dllexport)声明;2)为CDB类添加成员函数Release(),用于在对象不再被使用时删除自己;3)声明类工厂CDBSrvFactory;4)声明返回类工厂对象的引出函数DllGetClassFactoryObject,用于创建对应的类工厂

复制代码
typedef long HRESULT;

#define DEF_EXPORT _declspec(dllexport)

class CDB
 {
    // Interfaces
public:
    // Interface for data access
    HRESULT DEF_EXPORT Read(short nTable, short nRow, LPWSTR lpszData);
    HRESULT DEF_EXPORT Write(short nTable, short nRow, LPCWSTR lpszData);
    // Interface for database management
    HRESULT DEF_EXPORT Create(short &nTable, LPCWSTR lpszName);
    HRESULT DEF_EXPORT Delete(short nTable);
    // Interfase para obtenber informacion sobre la base de datos
    HRESULT DEF_EXPORT GetNumTables(short &nNumTables);
    HRESULT DEF_EXPORT GetTableName(short nTable, LPWSTR lpszName);
    HRESULT DEF_EXPORT GetNumRows(short nTable, short &nRows);
    ULONG DEF_EXPORT 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 
{
    // Interface
public:
    HRESULT DEF_EXPORT CreateDB(CDB** ppObject);
    ULONG   DEF_EXPORT Release();
};

HRESULT DEF_EXPORT DllGetClassFactoryObject(CDBSrvFactory ** ppObject);
复制代码
     二)修改对象程序。在上一节中,重用的对象是以DBSRV.h和DBSRV.cpp这两个文件形式存在的。这一次我们要将其封装为一个DLL供客户程序调用。
新建一个Win32 DLL项目,在其中加入两个cpp文件,一个用于实现CDB类,代码如下

复制代码
#include "..\interface\DBsrv.h"  //注意:接口头文件是DLL项目和客户程序共享的

// Database object
HRESULT CDB::Read(short nTable, short nRow, LPWSTR lpszData) 
{
    CStringArray *pTable;
    pTable=(CStringArray*) m_arrTables[nTable];
#ifndef UNICODE
    MultiByteToWideChar(CP_ACP, 0, (*pTable)[nRow], -1, lpszData, 80);
#else
    lstrcpy (lpszData, (*pTable)[nRow]);
#endif
    return NO_ERROR;
}

HRESULT CDB::Write(short nTable, short nRow, LPCWSTR lpszData)
{
    CStringArray *pTable;
    pTable=(CStringArray*) m_arrTables[nTable];
#ifdef UNICODE
    pTable->SetAtGrow(nRow, lpszData);
#else
    char szData[80];
    WideCharToMultiByte(CP_ACP, 0, lpszData, -1, szData, 80, NULL, NULL);
    pTable->SetAtGrow(nRow, szData);
#endif
    return NO_ERROR;
}
HRESULT CDB::Create(short &nTable, LPCWSTR lpszName)
{
    CStringArray *pTable=new CStringArray;
    nTable=m_arrTables.Add(pTable);
#ifdef UNICODE
     m_arrNames.SetAtGrow(nTable, lpszName);
#else
    char szName[80];
    WideCharToMultiByte(CP_ACP, 0, lpszName, -1, szName, 80, NULL, NULL);
    m_arrNames.SetAtGrow(nTable, szName);
#endif
    return NO_ERROR;
}

HRESULT CDB::Delete(short nTable) 
{
    CStringArray *pTable;
    pTable=(CStringArray*) m_arrTables[nTable];
    delete pTable;
    m_arrTables[nTable]=NULL;
    m_arrNames[nTable]="";
    if (nTable==m_arrTables.GetSize()-1) 
    {
        m_arrTables.RemoveAt(nTable);
        m_arrNames.RemoveAt(nTable);
    }
    return NO_ERROR;
}

HRESULT CDB::GetNumTables(short &nNumTables) 
{
    nNumTables=m_arrTables.GetSize();
    return NOERROR;
}

HRESULT CDB::GetTableName(short nTable, LPWSTR lpszName) 
{
#ifndef UNICODE
    MultiByteToWideChar(CP_ACP, 0, m_arrNames[nTable], -1, lpszName, 80);
#else
    lstrcpy(lpszName, m_arrNames[nTable]);
#endif
    return NO_ERROR;
}

HRESULT CDB::GetNumRows(short nTable, short &nRows) 
{
    CStringArray *pTable;
    pTable=(CStringArray*) m_arrTables[nTable];
    return pTable->GetSize();
}

ULONG CDB::Release() 
{
    delete this;
    return 0;
}

CDB::~CDB() 
{
  short nNumTables;
  for (GetNumTables(nNumTables);nNumTables>0; GetNumTables(nNumTables)) 
  {
      Delete(nNumTables-1);
  }
}

复制代码
在另一个DBSrvFact.cpp文件中实现类工厂:

 复制代码
#include "..\interface\dbsrv.h" //注意:接口头文件是DLL项目和客户程序共享的

// Create a new database object and return a pointer to it
HRESULT CDBSrvFactory::CreateDB(CDB** ppvDBObject) 
{
  *ppvDBObject=new CDB;
  return NO_ERROR;
}

ULONG CDBSrvFactory::Release() 
{
    delete this;
    return 0;
}

HRESULT DEF_EXPORT DllGetClassFactoryObject(CDBSrvFactory ** ppObject) 
{
    *ppObject=new CDBSrvFactory;
    return NO_ERROR;
}
复制代码
编译后生成引入库文件(.LIB)和动态链接库文件(.DLL)。

     三)修改客户程序

      1)由于前面我们已经为CDB类添加了删除自己的函数Release(),因此在CDBDoc的析构函数中修改我们使用的CDB对象的删除方式如下:

复制代码
CDBDoc::~CDBDoc()
{
    if (m_pDB) 
    {
        m_pDB->Release();//不再是delete m_pDB
        m_pDB=NULL;
    }
}
复制代码
 2)创建CDB类对象的方式改变了,我们通过对应的类工厂对象来创建CDB对象,而不再是直接地new一个CDB对象出来了。

复制代码
BOOL CDBDoc::OnNewDocument()
{
    if (!CDocument::OnNewDocument())
        return FALSE;
    //新建数据库对象
    //m_pDB=new CDB;
    CDBSrvFactory *pDBFactory=NULL; //对应的类工厂对象
    DllGetClassFactoryObject(&pDBFactory); //获取对应的类工厂
    pDBFactory->CreateDB(&m_pDB); //由类工厂负责创建所请求的对象
    pDBFactory->Release(); // do not need the factory anymore
    // 初始化数据成员变量
    …
}
复制代码
3)将传入/传出DLL中的参数标准化为Unicode编码。若不是以Unicode方式编译(##ifndef UNICODE),则使用MultiByteToWideChar将输出参数由ASCII转换为Unicode,用WideCharToMultiByte将输入参数由Unicode转换为ASCII。

 

复制代码
void CDBDoc::OnReadTable() 
{
#ifdef UNICODE
    m_pDB->Read(m_nTable, 0, m_csData.GetBuffer(80));
#else
    WCHAR szuData[80];
    m_pDB->Read(m_nTable, 0, szuData);
    WideCharToMultiByte(CP_ACP, 0, szuData, -1, m_csData.GetBuffer(80), 80, NULL, NULL);
#endif
    m_csData.ReleaseBuffer();
    UpdateAllViews(NULL);
}

void CDBDoc::OnWriteTable() 
{
    m_nCount++;
    CString csText;
    csText.Format(_T("Test data #%d in table %d, row 0!"), m_nCount, (int) m_nTable);
#ifdef UNICODE
    m_pDB->Write(m_nTable, 0, csText);
#else
    WCHAR szuText[80]; // special treatment for ASCII client
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, csText, -1, szuText, sizeof(szuText));
    m_pDB->Write(m_nTable, 0, szuText);
#endif
}
复制代码
4)连接DLL,创建客户程序。现在我们使用DLL,因此不再需要被重用对象的源代码,那么先将DBsrv.cpp和DBsrv.h两个文件从工程中删除。与DLL连接的方式采用隐式链接:在”链接器à输入à附加依赖项“中输入:.."object"Debug"db.lib。最后将DB.dll拷贝到客户程序目录下,运行客户程序。

     Ok,万里长征迈出了第一步…



本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2008/08/25/1275903.html,如需转载请自行联系原作者
目录
相关文章
|
2月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
166 77
|
2月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
85 19
|
2月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
60 10
|
2月前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
72 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
2月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
83 13
|
2月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
61 12
|
2月前
|
算法 C++
【C++数据结构——图】最小生成树(头歌实践教学平台习题) 【合集】
【数据结构——图】最小生成树(头歌实践教学平台习题)目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:【合集】任务描述 本关任务:编写一个程序求图的最小生成树。相关知识 为了完成本关任务,你需要掌握:1.建立邻接矩阵,2.Prim算法。建立邻接矩阵 上述带权无向图对应的二维数组,根据它建立邻接矩阵,如图1建立下列邻接矩阵。注意:INF表示无穷大,表示整数:32767 intA[MAXV][MAXV];Prim算法 普里姆(Prim)算法是一种构造性算法,从候选边中挑
49 10
|
2月前
|
存储 算法 C++
【C++数据结构——图】图的邻接矩阵和邻接表的存储(头歌实践教学平台习题)【合集】
本任务要求编写程序实现图的邻接矩阵和邻接表的存储。需掌握带权有向图、图的邻接矩阵及邻接表的概念。邻接矩阵用于表示顶点间的连接关系,邻接表则通过链表结构存储图信息。测试输入为图的顶点数、边数及邻接矩阵,预期输出为Prim算法求解结果。通关代码提供了完整的C++实现,包括输入、构建和打印邻接矩阵与邻接表的功能。
60 10
|
2月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
50 7
|
2月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
66 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】

热门文章

最新文章