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);
}

最后在CDBViewOnDraw函数中添加如下语句来显示读表读取到的内容:

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.cppDBsrv.h两个文件从工程中删除。与DLL连接的方式采用隐式链接:在链接器à输入à附加依赖项中输入:.."object"Debug"db.lib。最后将DB.dll拷贝到客户程序目录下,运行客户程序。

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

你可能感兴趣的:(part)