源代码下载 – 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,万里长征迈出了第一步…