ATL COM 和 ADO





4.00/5 (8投票s)
2000年12月15日

297494

1527
一个使用 ADO 进行数据访问的 ATL 组件。
引言
最近,我在 NIIT,班加罗尔进行了一个关于网上银行的项目。该项目主要使用 VB 编码,并加入了一些 ATL 组件,主要是为了教我们分布式应用程序的编程。我编写的一个中间层组件是用 ATL 构建的,并使用 ADO 来查询后端(SQL Server)。其中的一些代码在此处展示。
我假设读者知道或至少对使用 ATL 进行 COM 编程以及使用 VB 进行 ADO 编程有相当的了解。
什么是ADO?
ADO 代表 ActiveX Data Objects。ADO 提供了一个面向对象的编程接口,用于使用 OLEDB 数据提供程序访问数据源。它是 DAO 和 RDO 对象模型的后继者,并结合了 DAO 和 RDO 的最佳特性。
用 C++ 编程 OLEDB 很容易。但是,对于像 Visual Basic 这样不支持指针和其他 C++ 特性的语言来说,实现 OLEDB 是困难的。
这就是 ADO 真正发光的地方。ADO 是一个基于 COM 接口的高级 OLEDB 接口。因此,任何支持 COM 的应用程序都可以实现 ADO。
ADO 特性
- 访问所有类型的数据 - 各种数据源包括电子邮件、文本文件、RDBMS、ISAM/VSAM 数据库以及所有 ODBC 数据源。
- 支持自由线程 - ADO 支持通过多个线程进行多个客户端连接,并且这些线程之间不会相互干扰。
- 支持异步查询 - 这基本上意味着在 SQL 查询提交给数据库服务器后,控制立即返回给调用应用程序,允许用户在查询处理过程中继续工作。查询完成后,结果将发送到客户端。
- 支持客户端和服务器端游标 - 游标是一种允许访问和导航记录集中的数据的机制。它们作为客户端或服务器端实现。传统上,频繁更新的记录集实现为服务器端,而只读记录集实现为客户端。
- 支持断开的记录集 - 在查询执行后返回记录集后,它被存储为客户端游标,并关闭活动连接。在对记录集进行更改并提交后,重新建立连接,并将所有更新批量发送到数据存储。这在很大程度上减少了网络流量。
- 支持命令作为连接方法 - ADO 的一个独特之处在于,当执行命令时,会先在内部建立连接,然后才提交该命令以供执行。与 DAO/RDO 等传统对象模型相比,传统对象模型需要在提交命令之前显式建立连接。
ADO 体系结构
在 ADO 模型中,我们将使用三种主要类型的对象:
Connection
命令
Recordset
Connection 对象建立到数据源的连接。首先,数据源名称、位置、用户 ID、密码等存储在 ConnectionString 对象中,然后传递给 Connection 对象以建立到数据源的连接。
Command 对象用于执行 SQL 命令、查询和存储过程。
当执行查询时,它会返回存储在 Recordset 对象中的结果。记录集中的数据可以被操作,然后更新到数据库。
使用 ADO
首先,我们将构建一个 ATL DLL 组件。该组件有一个方法,该方法接受一个输入参数(在项目中是客户 ID),并将相应的 Recordset 对象引用返回给 VB 客户端。然后客户端会在窗体中显示数据。
要创建 DLL,请使用 ATL COM AppWizard 为应用程序生成框架。将项目命名为 FindCust,并选择服务器类型为 Dynamic Link Library。同时选择 support MFC library 选项。
向项目中插入一个 Simple Object 类型的 New ATL Object。在 ATL Object Wizard Properties 的 Short Name 文本框中使用名称 Search,然后单击 OK 添加对象。
在类视图中,右键单击接口名称并添加一个方法。将方法命名为 SearchCust
,然后在 Parameters 文本框中键入以下内容:
[in] BSTR bstrcustid,[out,retval] _Recordset **ptr
单击 OK 按钮添加方法。
由于 SearchCust
方法返回 Recordset 对象引用,因此我们需要导入 ADO 库。为此,请打开文件 StdAfx.h 并添加以下代码:
此步骤将帮助 Visual C++ 编译器理解类型库 MSADO15.DLL 中定义的 ADO 对象。rename_namespace
函数将 DLL 导入到的命名空间重命名为指定的名称。rename
选项已用于将 EOF 关键字重命名为 EndOfFile,因为 EOF 已在标准头文件中定义。
另外,.idl 文件包含返回 Recordset 对象引用的 SearchCust
方法。为了让 MIDL 编译器理解 ADO 对象,请使用 library
部分中的 importlib
语句(在 importlib "stdole2.tlb" 之后)导入 .idl 文件中的类型库:
importlib("C:\Program Files\Common Files\System\ADO\MSADO15.DLL");
同时,为了让 MIDL 编译器理解 ADO 对象,请将 .idl 文件中的接口定义移到 importlib
语句之后。
为此,请剪切接口定义块并将其粘贴到添加的 imporlib
语句之后。我的接口定义块看起来像:
[
object,
uuid(EB78D558-E071-4D25-80DD-41FD3519934E),
dual,
helpstring("ISearch Interface"),
pointer_default(unique)
]
interface ISearch : IDispatch
{
[id(1), helpstring("method SearchCust")] HRESULT SearchCust([in] BSTR
bstrcustid, [out,retval] _Recordset **ptr);
};
构建 ATL 组件
现在我们准备编写组件代码,其中包含 SearchCust
方法以检索相应的信息。我们需要做的是:
- 初始化 COM 库
- 连接到数据源
- 执行 SQL 命令
- 返回 Recordset 对象
- 反初始化 COM 库
初始化 COM 库
CoInitialize(NULL);
连接到数据源
首先声明一个 Connection 对象指针,传入 coclass 的 ID。
_ConnectionPtr conptr(__uuidof(Connection));
现在调用 Open 函数以建立到数据源的连接。
conptr->Open(_T("Provider=SQLOLEDB.1; Data Source=SQLServer;Initial Catalog=Customer"), _T("user1"),_T(""),adOpenUnspecified);
Open 函数接受四个参数。第一个是连接字符串,其中包含提供程序名称和用于连接的 SQL Server 名称。第二个和第三个参数是建立连接所需的用户名和密码。第四个参数是使用的游标类型。_T 宏确保字符串的 UNICODE 兼容性。
请注意,您的连接字符串将与我这里使用的不同。您可能需要使用其他提供程序以及连接到不同的数据源。显然,您的用户名和密码也会不同。Initial Catalog
参数定义了要使用的数据表。对于 SQL Server,OLEDB 提供程序是 SQLOLEDB。您也可以使用 MSDASQL 或 Microsoft Jet.OLEDB 提供程序连接到 MS Access 数据库(.mdb)。
执行 SQL 命令
要传递 SQL 命令,请创建一个 Command 对象指针,传入 Command 对象的 CLSID。
_CommandPtr cmd(__uuidof(Command));
将 Command 对象的 ActiveConnection
属性设置为已打开的 Connection 对象指针。
cmd->ActiveConnection=conptr;
现在将要执行的 SQL 语句存储在 Command 对象的 CommandText
属性中。
cmd->CommandText="<Your SQL statement goes here>"
返回 Recordset 对象
创建一个 Recordset 对象,并将 Command 对象指定为记录的源,如下所示:
_RecordsetPtr rst(__uuidof(Recordset)); rst->PutRefSource(cmd);
现在使用 Recordset 对象的 Open 方法打开 Recordset,如下所示:
_variant_v vNull; rst->Open(vNull,vNull,adOpenDynamic,adLockOptimistic,adCmdText);
Open
方法接受五个参数。第一个和第二个参数分别是数据源名称和要使用的活动连接。由于数据源已在 Connection
对象中指定,并且 ActiveConnection
属性已在 Command
对象中设置,因此第一个和第二个参数传递为 NULL 变体值。第三个参数指定要使用的游标类型,后跟锁定参数。第五个参数指定数据库应如何评估正在发送的命令。
现在创建的 Recordset 对象指针将具有对 SQL 语句返回的记录的引用。我们需要将此记录集返回给客户端。使用类似的代码:
rst->QueryInterface(__uuidof(_Recordset),(void **) ptr);
QueryInterface
函数接受 Recordset 对象的 IID,并返回对 SQL 语句返回的记录的引用。当客户端调用 SearchCust
方法时,此指针将返回给客户端。
反初始化 COM 库
::CoUninitialize();
现在构建组件。这将把 DLL 注册到注册表中。
构建 VB 客户端
打开 VB 并创建一个新的 Standard EXE 项目。通过 Project->References 设置对 Microsoft ActiveX Data Objects 2.1 Library 和 FindCust 1.0 Type Library 的引用。
可以使用以下 VB 代码来测试创建的 DLL 组件:
'declare a variable of the Object type Dim objCust as Object 'declare a variable of the type Recordset to store the value returned 'by the DLL Dim rst as ADODB.Recordset 'create an instance of the DLL i.e. FindCust.Search Set objCust = CreateObject("FindCust.Search") 'call the SearchCust method by passing the Customer ID 'Store the returned Recordset object Set rst = objCust.SearchCust(1) 'display information from the recordset in a message box MsgBox rst.Fields(1) & " " & rst.Fields(2)一旦获得 Recordset 对象,您就可以以任何您想要的方式操作和更新它包含的数据。例如,您可以使用
MoveFirst
、MoveNext
、MovePrevious
和 MoveLast
来导航记录集,并使用 Update
将数据更新到数据库。字段查找
记录集对象由一个 Field
对象集合组成,该集合构成一个 Fields
集合。Field 对象用于访问记录集中每条记录的字段。它们包含有关表中字段的名称、类型和值的信息。
为了说明字段查找,让我们考虑网上银行场景,其中我们需要为每个新客户生成一个唯一的账号。数据库的 Customer 表有一个账号字段 iAccountNumber,为简单起见,我们将其视为整数类型字段。
SQL 命令是:
select max(iAccountNumber)+1 from customer
执行此命令后,我们将从结果记录集中查找该值。
添加一个名为 GetMaxValue
的新方法,该方法带有一个参数 [out,retval] VARIANT *Val
。实现如下:
CoInitialize(NULL); ...//same as code previuosly ...//presented in article rst->Open(vNull,vNull,adOpenDynamic,adLockOptimistic,adCmdText); VARIANT index; VariantInit(&index); index.vt=VT_I4; index.lVal=0; FieldsPtr fields; FieldPtr field; HRESULT hr=rst->get_Fields(&fields); if(SUCCEEDED(hr)) { hr=fields->get_Item (index,&field); } if(SUCCEEDED(hr)) { hr=field->get_Value (Val); }
此记录集有一个字段(索引值为 0),代表最大账号。这就是我们要查找的值,现在存储在 Val 中。
好了,各位。现在你们正朝着 ADO 的名利双收之路前进!:-)
希望大家都觉得这篇文章有用。编程愉快!