数据库操作组件的ATL实现
COM表示Component Object Model即组件对象模型,是Microsoft生成软件组件的标准。它最大的优点在于它是二进制兼容软件组件的规范,即不论用什么语言生成COM组件,它都与其它COM组件兼容和可供其它组件使用。用VC++进行组件开发通常使用ATL (Active Template Library)来进行,在教材的第11章对数据库的操作我们使用了ADO技术,在此我们使用COM组件来实现数据库的操作。
下面详细介绍数据库操作组件的实现步骤:
1. 服务器端的实现
第一步建立ATL工程,选ATL Project(如图1-1所示),输入工程名字DBMan,接下来再选Dynamic-link library (DLL) 并保留所有默认选项,确定。编译该工程,会生成组件的宿主DLL和IDL相关文件。
图1-1
表1-1列举出了ATL AppWizard生成的工程文件:
文件名称 | 说明 |
DBMan.cpp | 主工程文件,包含了COM所需的支持函数,为组件提供宿主文件支持。 |
DBMan.h | 组件在宿主文件里的接口声明,MIDL编译器自动生成了该文件。编译工程的IDL文件就是为了生成该文件。 |
DBMan.idl | 工程的IDL文件,可以在该文件中添加接口和方法的定义。MIDL编译器处理该文件并产生工程的类型库文件。一个工程只能有一个IDL文件,工程中的所有组件共享该IDL文件。 |
DBMan.def | DLL工程的def文件。 |
DBMan_i.c | 该文件包含了工程中所有CLSID和IID的定义,由IDL文件经MIDL编译后生成。 |
DBMan_p.c | 工程的代理/驻留(proxy/stub)代码,由MIDL编译器生成。 |
Dlldata.c | 代理/驻留(proxy/stub)DLL文件的数据结构定义。 |
DBMan.rc | 工程的资源文件。 |
DBMan.rgs | 工程的注册脚本。 |
Resource.h | 工程的资源定义文件。 |
DBManps.def | DLL工程的扩展def文件。 |
Stdafx.h Stdafx.cpp | ATL的框架定义和包含信息。 |
表1-1
第二步添加DataAcqDBMan组件,选择Project | Add Class...菜单项,弹出Add Class对话框,选择ATL | ATL Simple Object选项,如图1-2 所示:
图1-2
点击Add按钮,弹出ATL Simple Object Wizard属性对话框,在Names属性页的Short name项键入DataAcqDBMan,该页面的其它项目会自动填入,如图1-3所示,其中上边C++组中,Short name为本页面的其它选项提供一个基础名称,Class是实现该组件的C++类的名称,
.h file 和.cpp file为组件类的头文件和实现文件名称。下边COM组中,Coclass表示COM类的名称,Interface为所创建对象的接口名称,Type是存放在注册表中可读的组件名称,ProgID是该组件的progid名称。
图1-3
点击Next按钮,弹出Options对话框,如图1-4所示,在Options属性对话框中可以给组件指定基本的COM支持选项,下边给出简单的描述。具体细节请参见MSDN文档。
图1-4
1. Threading model
a) Single,组件的实例只能在某一进程的主线程里创建
b) Apartment,组件的实例存在于其自身的单元线程中(Sigle Threaded Apartment, STA)
c) Free,组件必须和其他线程一起位于多线程单元里(MultiThreaded Apartment, MTA)
d) Neutral,指定对象遵守多线程单元的指南,但它可以在任何线程类型上执行
2. Interface
a) Custom,指定对象支持自定义接口(其vtable具有自定义接口函数)
b) Dual,即实现了vtable接口也实现标准的自动化接口
l Automation compatible,允许自动化控制器访问具有自定义接口支持的对象
3. Aggregation,该项技术可使得一个组件可以兼并或复用其他组件的功能
a) Yes,对组件可以被其他组件聚合(Aggregation)提供支持
b) No,不允许组件被其他组件聚合
c) Only,只允许组件的实例充当聚合
4.Support ISupportErrorInfo,该选项提供了一个服务器到客户端(Server to Client)的错误汇报机制
5.Free Threaded Marshaler,提供了在单进程的线程里以默认的方式对接口指针进行线程间的参数调度的支持
6.Connection points,提供了服务器的回叫信号或事件,使客户程序和服务器可以在对等的基础上彼此通讯
7. IObjectWithSite (IE object support),实现 IObjectWithSiteImpl,它为支持对象和它在容器中的站点之间的通信提供了一种简单的方法。
按照图1-3和图1-4所示进行设置,然后点击Finish按钮,图1-5显示了工程的变化。
图1-5
由ObjectWizard创建的文件:
文件名称 | 说明 |
DataAcqDBMan.h DataAcqDBMan.cpp | 组件对象的头文件和实现文件。 |
DataAcqDBMan.rgs | 组件对象的注册脚本。 |
第三步利用向导给IDataAcqDBMan接口添加方法,如图1-6所示:
图1-6
此接口共添加三个方法,实现如下:
1) 连接数据库
STDMETHODIMP CDataAcqDBMan::ConnectDB(BSTR ConnStr, BSTR UserId, BSTR Password, VARIANT_BOOL* pbResult)
{
// TODO: 在此添加实现代码
_bstr_t ConnectStr;
ConnectStr=ConnStr;
*pbResult = FALSE ;
HRESULT hr ;
try
{
hr = pConnection.CreateInstance(__uuidof(Connection));
if (hr == S_OK)
{
pConnection->Mode = adModeReadWrite;
pConnection->ConnectionTimeout = ConnectTimeOut;
pConnection->CursorLocation = adUseClient;
pConnection->Open(ConnectStr, UserId, Password, adOpenUnspecified);
if (pConnection->State == adStateOpen )
{
*pbResult = TRUE ;
}
}
}
catch(_com_error &e)
{
GenerateError(e.Description());
LogAdoErrorImport(pConnection);
return S_FALSE;
}
return S_OK;
}
2)断开与数据库的连接
STDMETHODIMP CDataAcqDBMan::DisConnect(void)
{
// TODO: 在此添加实现代码
try
{
if (pConnection->State == adStateOpen)
{
pConnection->Close() ;
pConnection = NULL;
}
}
catch(_com_error &e)
{
GenerateError(e.Description());
return S_FALSE;
}
return S_OK;
}
3)执行SQL语句
STDMETHODIMP CDataAcqDBMan::Execute(BSTR strSql, VARIANT_BOOL* pbResult)
{
// TODO: 在此添加实现代码
*pbResult = FALSE;
try
{
if(pConnection->Execute(strSql, NULL, -1))
*pbResult = TRUE;
}
catch(_com_error &e)
{
GenerateError(e.Description());
return S_FALSE;
}
return S_OK;
}
其它成员函数可参考示例代码和MSDN文档。
2.客户端的实现
1)初始化COM组件、创建实例
在应用程序类中添加成员函数 void CoInitInstance(void),按照示例代码录入函数的实现体。并在应用程序初始化时调用。
2)连接数据库
在主业务类中添加成员函数void ConnectToDb(),按照示例代码录入函数的实现体,并在主对话框初始化时调用。
3)数据库写日志操作
void CSysLogRec::BeginProcess() { VARIANT_BOOL pbResult = FALSE;
EnterCriticalSection(&CBaseThread::m_csNumLock); { m_pDBMan->Execute(_bstr_t(GetLogWriteSql()), &pbResult); if(!pbResult) AfxMessageBox(STR_LOGREC_ROUTE_ERR); } LeaveCriticalSection(&CBaseThread::m_csNumLock); } |
4)断开与数据库的连接、释放接口引用并删除COM初始化
在应用程序退出实例中添加以下代码:
int CDataAcqApp::ExitInstance() { // TODO: Add your specialized code here and/or call the base class CBaseThread::m_pDBMan->DisConnect(); CBaseThread::m_pDBMan->Release(); CoUninitialize(); DeleteCriticalSection(&CBaseThread::m_csNumLock); CloseHandle(CBaseThread::m_hAnotherDead); return CWinApp::ExitInstance(); } |
其它成员函数的使用方法可参考MSDN和实例程序.
示例程序附后.