一 引入ADO类型库
Activex Objects Model首先是一组COM对象模型。所以通常我们需要引入其类型库,以下是引入类型库的语句
#import
"c:/program files/common files/system/ado/msado15.dll" no_namespace raw_interfaces_only rename ("EOF","ADOEOF")
这里
no_namespace 表示引入的类型不会放在名字空间里。raw_interfaces_only表示只使用COM的原始接口方法调用,而不使用包装接口。区别就是包装接口总是用异常的方式报错,而原始接口方法通过返回HRESULT值报告信息;同时,参数类型和数目略有区别。
编译器将生成
msado15.tlh文件,该文件包含了ADO中的各种类型
二创建与数据库的连接
首先我们声明连接对象。msado15.tlh文件中对于连接对象如下说明,
struct
__declspec(uuid("00000514-0000-0010-8000-00aa006d2ea4"))
Connection;
// [ default ] interface _Connection
// [ default, source ] dispinterface ConnectionEvents
以下是创建连接对象的代码:
CComPtr<_Connection> pCon;
HRESULT hr=pCon.CoCreateInstance(__uuidof(Connection));
或者 HRESULT hr=pCon.CoCreateInstance(L"ADODB.Connection");
很多书上介绍使用_ConnectionPtr,实际上都是智能指针,只是个人习惯而已,这些智能指针类通过重载operator ->()函数暴露了接口方法。
然后使用
udl文件和某数据库连接,我非常喜欢使用udl文件的方式,而不喜欢使用字符串与数据库连接,因为如果不小心差了一个空格,数据库连接就会出错,有时候错误一开始不会发生,但是说不定哪天就冒出来了。所以我的文章最后会给出数据库的连接字符串,但是不推荐使用。
pCon->put_ConnectionString(CComBSTR(L"File Name=c://fqw.udl"));
hr=pCon->Open(L"",L"",L"",NULL);
三使用_Connection接口
Connection;
// [ default ] interface _Connection
// [ default, source ] dispinterface ConnectionEvents
通过
msado15.tlh文件中的片断我们可以看出Connection对象实现了_Connection接口
查看_Connection接口的定义:
struct
__declspec(uuid("00000534-0000-0010-8000-00aa006d2ea4"))
_ADO : IDispatch
{
//
// Raw methods provided by interface
//
virtual HRESULT __stdcall get_Properties (
/*[out,retval]*/ struct Properties * * ppvObject ) = 0;
};
struct
__declspec(uuid("00000515-0000-0010-8000-00aa006d2ea4"))
Connection15 : _ADO
{
//
// Raw methods provided by interface
//
virtual HRESULT __stdcall get_ConnectionString (
/*[out,retval]*/ BSTR * pbstr ) = 0;
virtual HRESULT __stdcall put_ConnectionString (
/*[in]*/ BSTR pbstr ) = 0;
virtual HRESULT __stdcall get_CommandTimeout (
/*[out,retval]*/ long * plTimeout ) = 0;
virtual HRESULT __stdcall put_CommandTimeout (
/*[in]*/ long plTimeout ) = 0;
virtual HRESULT __stdcall get_ConnectionTimeout (
/*[out,retval]*/ long * plTimeout ) = 0;
virtual HRESULT __stdcall put_ConnectionTimeout (
/*[in]*/ long plTimeout ) = 0;
virtual HRESULT __stdcall get_Version (
/*[out,retval]*/ BSTR * pbstr ) = 0;
virtual HRESULT __stdcall Close ( ) = 0;
virtual HRESULT __stdcall Execute (
/*[in]*/ BSTR CommandText,
/*[out]*/ VARIANT * RecordsAffected,
/*[in]*/ long Options,
/*[out,retval]*/ struct _Recordset * * ppiRset ) = 0;
virtual HRESULT __stdcall BeginTrans (
/*[out,retval]*/ long * TransactionLevel ) = 0;
virtual HRESULT __stdcall CommitTrans ( ) = 0;
virtual HRESULT __stdcall RollbackTrans ( ) = 0;
virtual HRESULT __stdcall Open (
/*[in]*/ BSTR ConnectionString,
/*[in]*/ BSTR UserID,
/*[in]*/ BSTR Password,
/*[in]*/ long Options ) = 0;
virtual HRESULT __stdcall get_Errors (
/*[out,retval]*/ struct Errors * * ppvObject ) = 0;
virtual HRESULT __stdcall get_DefaultDatabase (
/*[out,retval]*/ BSTR * pbstr ) = 0;
virtual HRESULT __stdcall put_DefaultDatabase (
/*[in]*/ BSTR pbstr ) = 0;
virtual HRESULT __stdcall get_IsolationLevel (
/*[out,retval]*/ enum IsolationLevelEnum * Level ) = 0;
virtual HRESULT __stdcall put_IsolationLevel (
/*[in]*/ enum IsolationLevelEnum Level ) = 0;
virtual HRESULT __stdcall get_Attributes (
/*[out,retval]*/ long * plAttr ) = 0;
virtual HRESULT __stdcall put_Attributes (
/*[in]*/ long plAttr ) = 0;
virtual HRESULT __stdcall get_CursorLocation (
/*[out,retval]*/ enum CursorLocationEnum * plCursorLoc ) = 0;
virtual HRESULT __stdcall put_CursorLocation (
/*[in]*/ enum CursorLocationEnum plCursorLoc ) = 0;
virtual HRESULT __stdcall get_Mode (
/*[out,retval]*/ enum ConnectModeEnum * plMode ) = 0;
virtual HRESULT __stdcall put_Mode (
/*[in]*/ enum ConnectModeEnum plMode ) = 0;
virtual HRESULT __stdcall get_Provider (
/*[out,retval]*/ BSTR * pbstr ) = 0;
virtual HRESULT __stdcall put_Provider (
/*[in]*/ BSTR pbstr ) = 0;
virtual HRESULT __stdcall get_State (
/*[out,retval]*/ long * plObjState ) = 0;
virtual HRESULT __stdcall OpenSchema (
/*[in]*/ enum SchemaEnum Schema,
/*[in]*/ VARIANT Restrictions,
/*[in]*/ VARIANT SchemaID,
/*[out,retval]*/ struct _Recordset * * pprset ) = 0;
};
struct
__declspec(uuid("00000550-0000-0010-8000-00aa006d2ea4"))
_Connection : Connection15
{
//
// Raw methods provided by interface
//
virtual HRESULT __stdcall Cancel ( ) = 0;
};
_Connection接口继承了
Connection15接口,Connection15 又继承了_ADO。_ADO接口是绝大多数ADO接口的基接口。
使用_Connection接口执行SQL语句
CComBSTR SQL(L"DELETE FROM [15]");
hr=pCon->Execute(SQL,NULL,adCmdText|adExecuteNoRecords,NULL);
Execute也可以执行查询语句返回结果记录集
,第三个参数取消
adExecuteNoRecords
,第四个参数将
_Recordset的指针地址传递进来即可。
MDAC2.0开始引入了连接池的概念,ODBC中使用了connection pooling,OLEDB和ADO中使用了resource pooling。
ADO中,如果连接字符串相同,但是动态属性值不一样(比如:PROMPT),MDAC仍然会创建两个用户身份。连接池的用户身份和操作系统的登陆账号不是一回事,它取决于连接字符串和任何在连接对象打开之前的设置的属性值。如果客户程序使用同样的用户身份和连接属性对同一个数据存储请求连接对象,连接池中的合适的连接对象将被激活。连接对象被释放后会返回连接池中,经过60秒不被激活,会被自动销毁。
ADO的连接对象实际上内部使用数据源代理对象,如果在连接对象关闭状态下设置连接对象的属性,则属性值将缓存在本地的数据源代理对象中。当打开连接对象时会检查resource pooling中是否有和代理请求的相同的数据源代理对象,如果有则使用池中的,否则创建。所有resource pooling只存放数据源代理对象,不会存放其他的资源。MDAC2.5版本之前不可以修改resource pooling中的数据源代理对象实效时间。
ADO默认会启用resource pooling。但是如果客户程序不能保持至少一个连接对象,则
连接池不会生存。这里可以考虑使用COM+对象池保存连接对象。
打开的连接对象一定要通过Close关闭,不要忘记,否则连接对象不能返回resourse pooling中。将导致性能降低。
不过最终决定不再使用连接对象,应该调用Release方法释放。这是COM的规则。
_Connection::put_ConnectionString方法用来设置连接字符串,但是注意以下几点:
1)
_Connection::Open打开时如果第一个参数非空,该参数也可以设置或者覆盖ConnectionString,
2)如果使用UDL文件,如前面所举的例子,_Connection::get_ConnectionString获取到的不是UDL文件名,而是UDL文件的属性,比如:
“Provider=SQLOLEDB.1;
Password=sa;
Persist Security Info=True;
User ID=sa;
Initial Catalog=ff;
Data Source=FREEBIRD;
Use Procedure for Prepare=1;
Auto Translate=True;
Packet Size=4096;
Workstation ID=FREEBIRD;
Use Encryption for Data=False;
Tag with column collation when possible=False“
3)连接字符串使用”参数=参数值;”方式构成,ADO支持五种参数,其他由数据提供者提供:Provider 、File Name、Remote Provider、Remote Server、URL。ADO只是将其他参数直接传递给数据提供者,自己并不做任何处理。
COM+对象池保存Connection对象
近一年多来我本人几乎从来不在客户程序中调用ADO,这与从事的编程工作多为系统开发有关。系统开发讲究服务器资源的负荷不能超过极限,讲究客户调用服务快进快出等。在微软的平台上开发DNA系统一定要借助于COM+。所以我重点讨论COM+中如何运用ADO编程,这就是本文与一般的ADO文章的最大不同。
我将在
COM+对象CMIS类中维护一个局部变量
CComPtr<_Connection> m_pCon;我准备将该COM+对象配置为支持对象池和JITA。关于COM+对象池和JITA可以参考我的另一篇文章《COM+编程研究之对象池、JITA》,这里不做说明。
由于_Connection接口指针每次当方法调用后会被放入对象池中,而下一次调用它的客户将来自另一个COM+上下文,所以必须经过轻量列集,而不能直接使用m_pCon,所以我们需要
1) 在GIT中保存_Connection接口;
2) 在每次方法调用时从GIT中散集该接口;
3) 在每次方法调用后释放该接口;
4) 在最后对象被释放的时候要将GIT中保存的接口指针删除。
请参考下面的源代码示例:
创建Connection对象,并将获取的_Connection接口指针保存到GIT中,m_Cookie用来以后从GIT中取出该接口指针。
HRESULT FinalConstruct()
{
HRESULT hr=m_pCon.CoCreateInstance(__uuidof(Connection));
if(hr!=S_OK)
return hr;
hr=m_pCon->put_ConnectionString(CComBSTR(L"File Name=c://
海塘.udl"));
if(hr!=S_OK)
return hr;
hr=m_pCon->Open(L"",L"",L"",NULL);
if(hr!=S_OK)
return hr;
CComPtr<IGlobalInterfaceTable> pGTable;
hr=pGTable.CoCreateInstance(CLSID_StdGlobalInterfaceTable);
if(hr!=S_OK)
return hr;
hr=pGTable->RegisterInterfaceInGlobal(m_pCon.p,__uuidof(_Connection),&m_Cookie);
m_pCon.Release();
return hr;
}
方法调用时从GIT中列集_Connection指针:
HRESULT CMIS::Activate()
{
HRESULT hr = GetObjectContext(&m_spObjectContext);
if (hr!=S_OK)
return hr;
CComPtr<IGlobalInterfaceTable> pGTable;
hr=pGTable.CoCreateInstance(CLSID_StdGlobalInterfaceTable);
if(hr!=S_OK)
return hr;
hr=pGTable->GetInterfaceFromGlobal(m_Cookie,__uuidof(_Connection),(void**)&m_pCon.p);
if(hr!=S_OK)
return hr;
return S_OK;
}
设定方法调用后总是返回到对象池
BOOL CMIS::CanBePooled()
{
return TRUE;
}
方法调用后将当前
_Connection接口释放
void
CMIS::Deactivate()
{
m_pCon.Release();
m_spObjectContext.Release();
}
我们如何让我们的COM+对象CMIS总是拥有至少一个ADO连接对象呢?很简单,将COM+对象池下限设为1。
列集_Recordset接口指针到客户程序:
话题才刚刚开始。我打算用以下方法进行测试,CMIS对象提供一个方法
GetShuJuZiDian从ff数据库中的数据字典表中读入所有数据,该方法将把_Recordset接口指针传递出去,客户需要自行导入ADO15.DLL中的类型库信息才能使用。该_Recordset是从Connection对象的Execute方法获取的,因此是只读并且向前移动的,这样正好,因为我不想让客户获得过大的权力。
IDL如下:
interface
IMIS : IDispatch{
[id(1), helpstring("
方法将传递一个_Recordset接口指针,传递出去的记录集对象是只读并且只能向前移动")] HRESULT GetShuJuZiDian([out]IUnknown** pRec);
};
代码实现如下:
STDMETHODIMP CMIS::GetShuJuZiDian(IUnknown** pRec)
{
//The returned Recordset object is always a read-only, forward-only cursor.
CComBSTR SQL(L"select * from
数据字典表");
HRESULT hr=m_pCon->Execute(SQL,NULL,adCmdText,(_Recordset**)pRec);
return hr;
}
客户端调用代码如下:
#include
"stdafx.h"
#import
"haitangmis.tlb" no_namespace raw_interfaces_only named_guids
#include
<iostream>
using
namespace std;
#include
<atlstr.h>
int
_tmain(int argc, _TCHAR* argv[])
{
::CoInitialize(NULL);
CComPtr<IMIS> pMIS;
HRESULT hr=pMIS.CoCreateInstance(CLSID_MIS);
IUnknown* pIUn=NULL;
hr=pMIS->GetShuJuZiDian(&pIUn);
VARIANT_BOOL vResult;
CComPtr<_Recordset> pRec;
hr=pIUn->QueryInterface(__uuidof(_Recordset),(void**)&pRec.p);
hr=pRec->get_ADOEOF(&vResult);
CComPtr<Fields> pFields;
CComPtr<Field> pField;
CComVariant Value;
while(vResult==VARIANT_FALSE)
{
hr=pRec->get_Fields(&pFields.p);
hr=pFields->get_Item(CComVariant(0),&pField);//
CComBSTR Name;
pField->get_Name(&Name);
cout<<CString(Name)<<endl;
hr=pField->get_Value(&Value);
hr=Value.ChangeType(VT_BSTR);
cout<<CString(Value.bstrVal)<<endl;
hr=pRec->MoveNext();
pField.Release();
pFields.Release();
hr=pRec->get_ADOEOF(&vResult);
}
hr=pRec->Supports(adMovePrevious,&vResult);
hr=pRec->Supports(adUpdate,&vResult);
pRec.Release();
pIUn->Release();
pMIS.Release();
::CoUninitialize();
int x;
cin>>x;
return 0;
}
freebird
5/16/2005