VC++程序员应当如何阅读ADO文档

 
【导读】本文是对于 C C++ 程序员使用 ADO #import 指示符方面的概述,主要描述了 COM 使用的数据类型 (Variant, BSTR, and SafeArray) 和异常的处理 (_com_error)
VC++ 程序员应当如何阅读 ADO 文档
ADO API 参考》用 VB 的语法描述了 ADO API 的内容。但 ADO 程序员却使用着不同的编程语言,比如 VB,VC++ VJ++ 。对此《 ADO for VC++ 的语法索引》提供了符合 VC++ 语法规范的详细描述,包括功能、参数、异常处理等等。
ADO 基于若干的 COM 借口实现,因此它的使用对于一个正进行 COM 编程的程序员而言更简单。比如,几乎所有使用 COM 的细节对于 VB 程序员而言都是隐藏了的,但对于 VC++ 程序员而言却要特别注意。以下是对于 C C++ 程序员使用 ADO #import 指示符方面的概述,主要描述了 COM 使用的数据类型 (Variant, BSTR, and SafeArray) 和异常的处理 (_com_error)
 
使用 #import 编译指示符
#import 编译指示符使使用 ADO 的方法与属性简单化。这个指示符需要一个类型库文件名,比如 ADO.dll(Msado15.dll) ,并生成对应的头文件,其中包括定义的类型、接口的智能化指针、常量。并且所有的接口都被封装成类。
对于类中的每个操作(或称方法、属性调用),都有一个声明以保证能直接调用它(或称作操作的源形式),以及另一个声明来调用这个源操作并在操作失败时抛出一个 COM 错误。如果操作是一个属性,那么编译指示符可以为该操作创建一个可交互的类似 VB 的语法形式。
返回 / 设置属性的操作有对应的形式化的名字— GetProperty/PutPropert ,而设置一个指向某个 ADO 对象的指针型属性值时则是 PutRefProperty 。你将使用如下的形式读写属性的值:
variable = objectPtr->GetProperty(); // 读取属性的值
objectPtr->PutProperty(value);       // 设置属性的值
objectPtr->PutRefProperty(&value);   // 设置一个指针型的属性的值
 
直接使用属性
__declspec(property...) 编译指示符是微软定义的一个针对 C 语言的扩展,使一个函数象一个属性那样被使用。这样你就可以采用如下的语法形式象在使用 VB 一样读写一个属性的值: objectPtr->property = value;        // 设置属性的值
variable = objectPtr->property;     // 读取属性的值
__declspec(property...) 编译指示符只能针对属性的读写函数使用,并根据属性是否可供读写自动生成对应的调用形式。每个属性可能有 GetProperty, PutProperty,PutRefProperty 三个函数,但这个编译符只能生成其中的两种交互形式。比如, Command 对象的 ActiveConnection 属性有 GetActiveConnection PutRefActiveConnection 这两个读写函数。而 PutRef- 的形式在实践中是个好的选择,你可以将一个活动的 Connection 对象的指针保存在这个属性中。另一方面, Recordset 对象则有 Get-, Put-, and PutRefActiveConnection 操作,但却没有可交互的语法形式。
 
Collections,GetItem 方法和 Item 属性
ADO 定义了几种集合 Collection ,包括 Fields,Parameters,Properties, Errors 。在 Visual C++ 中, GetItem(index) 方法返回 Collection 中的某个成员。 Index 是一个 Variant 型的参数,内容可以是一个该成员对应的序数,也可以是一个包括其名称的字符串。
__declspec(property...) 编译指示符为 Item 属性生成对应于 GetItem() 方法的直接使用形式(上文提到的可交互的语法形式)。这种形式类似于引用数组元素时使用 [] 的语法形式:
collectionPtr->GetItem(index);
collectionPtr->Item[index];
举例说明,要给一个 Recordset 对象 rs 中的某个字段赋值,而这个 Recordset 对象派生于 pubs 数据库中的 authors 表。使用 Item() 属性访问这个 Recordset Fields 集合中的第三个字段(集合总是从 0 开始编号,假设第三个字段名为 au_fname )。然后调用 Value() 方法为该字段赋一个字符串值。
Visual Basic 的语法形式:
rs.Fields.Item(2).Value = "value"
rs.Fields.Item("au_fname").Value = "value"
或者:
rs(2) = "value"
rs!au_fname = "value"
Visual C++ 的语法形式:
rs->Fields->GetItem(2)->PutValue("value");
rs->Fields->GetItem("au_fname")->PutValue("value");
或者:
rs->Fields->Item[2]->Value = "value";
rs->Fields->Item["au_fname"]->Value = "value";
 
COM 特定的数据类型
一般的,你在《 ADO API Reference 》中看到的 VB 的数据类型在 VC++ 中也能找到对应的类型。其中包括标准的数据类型,比如 unsigned char 对应 VB Byte short 对应 Integer,long 对应 Long 。参见《 Syntax Indexes 》将可以获得关于所需操作数的更详细内容。
而作为例外的专属于 COM 使用的数据类型则有: Variant, BSTR, and SafeArray.
 
Variant
Variant 是一个结构化的数据类型,包含了一个成员值及其数据类型的表示。 Variant 可以表示相当多的数据类型,甚至另一个 Variant, BSTR, Boolean, Idispatch Iunknown 指针,货币,日期等等。同时 COM 也提供了许多方法使数据类型间的转换更简单化。
_variant_t 类封装并管理 Variant 这一数据类型。
当《 ADO API Reference 》中说到一个方法或属性要使用一个参数时,通常意味着需要一个 _variant_t 类型的参数。这条准则在《 ADO API Reference 》的 Parameters 一章中得到了明白无误的表述。作为例外的是,有时则会要求操作数是一个标准的数据类型,比如 Long Byte, 或者一个枚举值。另一个例外是要求操作数是一个字符串 String
 
BSTR
BSTR (Basic STRing) 也是一个结构化的数据类型,包括了串及串的长度。 COM 提供了方法进行串的空间分配、操作、释放。
_bstr_t 类封装并管理 BSTR 这一数据类型。
当《 ADO API Reference 》中说到一个方法或属性要使用一个字符串参数时,通常意味着需要一个类 _bstr_t 型的参数。
 
_variant_t _bstr_t 类的强制类型转换
通常当传递一个 _variant_t _bstr_t 参数给一个操作时并不需要显式的类型转换代码。如果 _variant_t _bstr_t 类提供了对应于该参数类型的构造函数,那么编译器将会自动生成适当的 _variant_t _bstr_t 值。
然而,当参数模棱两可时,即对应了多个构造函数时,你就必须显式地调用正确的构造函数以获得正确的参数。比如, Recordset::Open 方法的函数声明如下:
    HRESULT Open (
        const _variant_t & Source,
        const _variant_t & ActiveConnection,
        enum CursorTypeEnum CursorType,
        enum LockTypeEnum LockType,
        long Options );
其中参数 ActiveConnection 就是针对一个 variant_t 型变量的引用,它可以是一个连接串或者一个指向已打开的 Connection 对象的指针。
正确的 _variant_t 型参数会被构造,无论你传递的是一个类似 "DSN=pubs;uid=sa;pwd=;" 这样的字符串,或者是一个类似 "(IDispatch *) pConn" 的指针。
或者你还可以显式的编写 "_variant_t((IDispatch *) pConn, true)" 这样的代码来传递一个包含指针的 _variant_t 变量。这里的强制类型转换 (IDispatch *) 避免了可能调用 IUnknown 接口构造函数的模棱两可性。
虽然很少提及但特别重要的是, ADO 总是一个 IDispatch 接口。任何被传递的被包含在 Variant 中的指针都必须被转换为一个 IDispatch 接口指针。
最后需要说明的是构造函数的第二个逻辑参数是可选择的,它的缺省值是 True 。这个参数将决定 Variant 的构造函数是否调用内嵌的 AddRef() 方法,并在完成 ADO 的方法或属性调用后是否自动调用 _variant_t::Release() 方法
 
SafeArray
SafeArray 也是一种结构化的数据类型,包含了一个由其它数据类型的数据元素组成的数组。之所以称之为安全的数组是因为它包含了每一维的边界信息,并限制在边界内进行数组元素的访问。
当《 ADO API Reference 》中说到一个方法或属性要使用或者返回一个数组时,通常意味着是一个 SafeArray 数组,而非一个本地化的 C/C++ 数组。
比如, Connection 对象的 OpenSchema 方法的第二个参数需要一个由 Variant 值组成的数组。这些 Variant 值必须作为一个 SafeArray 数组的元素进行传递。而这个 SafeArray 数组本身又被作为一个 Variant 进行传递。
更进一步的, Find 方法的第一个参数是一个指向一维 SafeArray 数组的 Variant AddNew 方法的可选的第一与第二个参数也是一个一维的 SafeArray 数组; GetRows 方法的返回值则是一个包含二维 SafeArray 数组的 Variant
 
缺省参数
VB 允许省略方法的某些参数。例如, Recordset 对象的 Open 方法有五个参数,但是你可以跳过中间的参数并省略之后的参数。被省略的参数会被自动创建的 BSTR Variant 缺省值替代。
C/C++ 中,所有的操作数必须被明确。如果你想定义一个字符串型的缺省参数,那么就定义一个包含空字符串的 _bstr_t 。如果想定义一个 Variant 型的缺省参数,那么就定义一个值为 DISP_E_PARAMNOTFOUND 、类型为 VT_ERROR _variant_t 。你还可以使用 #import 编译指示符提供的与之等价的常量 vtMissing
vtMissing 的使用有三种意外情形: Connection Command 对象的 Execute 方法, Recordset 对象的 NextRecordset 方法。
_RecordsetPtr Execute( _bstr_t CommandText, VARIANT * RecordsAffected,
        long Options );  // Connection
_RecordsetPtr Execute( VARIANT * RecordsAffected, VARIANT * Parameters,
        long Options );  // Command
_RecordsetPtr NextRecordset( VARIANT * RecordsAffected );  // Recordset
参数 RecordsAffected Parameters 都是指向 Variant 的指针。 Parameters 是一个传入参数,指向一个包含一个或一组参数信息的 Variant 的地址,将决定命令执行的内容。 RecordsAffected 是一个传出参数,指向一个包含该方法返回时影响行的数目的 Variant 的地址。
Command 对象的 Execute 方法中,如果只是没有参数的话,需要将 Parameters 设置为 &vtMissing ( 推荐使用 ) 或者一个空指针 (NULL) 。如果传递的是一个空指针,那么等价的 vtMissing 会被传递并完成操作。
在所有的方法中,通过设置 RecordsAffected 为空指针可以指示不需返回被影响的记录的数目。此时,这个空指针实际成为了指示该方法抛弃被影响记录数目的指示器。
因此,如下的编码是有效的:
pConnection->Execute("commandText", NULL, adCmdText);
pCommand->Execute(NULL, NULL, adCmdText);
pRecordset->NextRecordset(NULL);
 
错误的处理
COM 中,大多数的操作总是返回一个 HRESULT 值说明该函数是否被成功完成。编译指示符 #import 为所有源方法和属性提供了封装好的代码并检查返回的 HRESULT 值。如果 HRESULT 指示失败,这些封装代码将会通过调用以 HRESULT 为参数的 _com_issue_errorex() 抛出一个 COM 错误。 COM 错误对象将在 try-catch 块中被捕获(出于效率的考虑,实际捕获的是一个 _com_error 对象的引用指针)。
记住,由 ADO 操作失败产生的错误才是 ADO 错误。由下层提供者返回的错误以 Connection 对象中 Errors 集合中的一个 Error 对象的形式出现。
编译指示符 #import 只能为在 ADO.dll 中声明的方法和属性提供错误处理例程。因此,你可以基于同样的错误处理机制编写自己的错误检查宏或内置函数。参见《 Visual C++ 扩展》以及本文后续的示例代码。
 
VC++ VB 中编码时的约定
下面是 ADO 文档中关于如何使用 VB VC++ 编写代码的一个概览。
 
声明一个 ADO 对象
VB 中,一个 ADO 对象变量(此处以 Recordset 对象为例)如下声明:
Dim rst As ADODB.Recordset
子句 "ADODB.Recordset" 是在注册表中登记的 Recordset 对象的 ProgID 。而一个 Record 对象的实例如下声明: Dim rst As New ADODB.Recordset
或者:
Dim rst As ADODB.Recordset
Set rst = New ADODB.Recordset
而在 VC++ 中, #import 为所有的 ADO 对象生成了智能的指针类型。比如一个指向 _Recordset 对象的指针变量的数据类型为 _RecordsetPtr ,并如下声明:
_RecordsetPtr  rs;
而一个 _Recordset 对象的实例则如下声明:
_RecordsetPtr  rs("ADODB.Recordset");
或者:
_RecordsetPtr  rs;
rs.CreateInstance("ADODB.Recordset");
或者:
_RecordsetPtr  rs;
rs.CreateInstance(__uuidof(_Recordset));
CreateInstance 方法被成功调用后,该变量可被如此使用: rs->Open(...);
注意,如果变量是一个类的实例则用 "." 操作符,若是一个指向实例的指针则应使用 "->" 操作符。
一个变量能通过两种方式被使用。因为 "->" 操作符被重载,允许一个对象实例类似一个接口指针那样被使用; "->" 操作符返回该指针;而由这个返回的指针访问 _Recordset 对象的成员。
 
编写省略 String 参数的代码
当你需要利用 VB 编写省略 String 参数的代码时,只需简单的略掉该操作数即可。但在 VC++ 中,你必须指定该操作数为一个包含空字符串的 _bstr_t 变量: _bstr_t strMissing(L"");
 
编写省略 Variant 参数的代码
当你需要利用 VB 编写省略 Variant 参数的代码时,只需简单的略掉该操作数即可。但在 VC++ 中,你必须指定所有的操作数。编写省略 Variant 参数的代码只需将该 Variant 设为专门的值,可以定义一个值为 DISP_E_PARAMNOTFOUND 、类型为 VT_ERROR _variant_t 。还可以使用 #import 编译指示符提供的与之等价的常量 vtMissing
_variant_t  vtMissingYours(DISP_E_PARAMNOTFOUND, VT_ERROR);
或者:
...vtMissing...;
 
声明一个 Variant
VB 中,一个 Variant 如下被声明:
Dim VariableName As Variant
VC++ 中,定义一个 _variant_t 型的变量即可。主要有以下几种形式。注意:这些声明只是你在变成时刻采用的一个粗略的思路。
_variant_t  VariableName(value);
_variant_t  VariableName((data type cast) value);
_variant_t  VariableName(value, VT_DATATYPE);
_variant_t  VariableName(interface * value, bool fAddRef = true);
 
使用 Variants 数组
VB 中,利用 Dim 语句可以进行 Variant 数组的编程,并可以使用 Array 的函数。见如下示例:
Public Sub ArrayOfVariants
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim fld As ADODB.Field
 
cn.Open "DSN=pubs", "sa", ""
rs = cn.OpenSchema(adSchemaColumns, _
                     Array(Empty, Empty, "authors", Empty))
For Each fld in rs.Fields
   Debug.Print "Name = "; fld.Name
Next fld
rs.Close
cn.Close
End Sub
以下的代码演示了如何通过一个 _variant_t 使用一个 SafeArray 数组。注意注释对应了编码的步骤。
1 .再一次的, TESTHR() 内置函数被定义以利用预存的错误处理机制。
2 .如果你只需要一个一维数组,你可以使用 SafeArrayCreateVector, 而非 SAFEARRAYBOUND 声明与 SafeArrayCreate 函数。下面的代码使用了 SafeArrayCreate
   SAFEARRAYBOUND   sabound[1];
   sabound[0].lLbound = 0;
   sabound[0].cElements = 4;
   pSa = SafeArrayCreate(VT_VARIANT, 1, sabound);
3 .枚举常量 adSchemaColumns 定义的模式,决定了与 TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME COLUMN_NAME 四列相联系。为此,一个有四个 Variant 元素的数组被创建。而对应于第三列 TABLE_NAME 的值被设置。
由若干列组成的返回的 Recordset 只是对应的所有列的一个子集,并且每一行的值保持了一一对应。
4 .熟悉 SafeArrays 的人也许会对退出前没有调用 SafeArrayDestroy() 感到惊奇。实际上,在这种情况下调用 SafeArrayDestroy() 会导致一个运行时的异常发生。这是因为 vtCriteria 的析构函数会在 _variant_t 超出使用范围时调用 VariantClear() ,从而释放 SafeArray 。只调用 SafeArrayDestroy ,而没有手动清除 _variant_t ,将会导致析构函数试图去清除一个无效的 SafeArray 指针。如果要调用 SafeArrayDestroy() ,那么代码应该象这样:
   TESTHR(SafeArrayDestroy(pSa));
   vtCriteria.vt = VT_EMPTY;
   vtCriteria.parray = NULL;
实际更像是让 _variant_t 管理 SafeArray
 
完整的代码如下:
#import "c:/Program Files/Common Files/System/ADO/msado15.dll" /
   no_namespace rename("EOF", "EndOfFile")
#include <stdio.h>
 
// Note 1
inline void TESTHR( HRESULT _hr )
   { if FAILED(_hr) _com_issue_error(_hr); }
 
void main(void)
{
   CoInitialize(NULL);
   try
   {
   _RecordsetPtr   pRs("ADODB.Recordset");
   _ConnectionPtr  pCn("ADODB.Connection");
   _variant_t      vtTableName("authors"),
                   vtCriteria;
   long            ix[1];
   SAFEARRAY       *pSa = NULL;
 
   pCn->Open("DSN=pubs;User ID=sa;pwd=;Provider=MSDASQL;", "", "",
               adConnectUnspecified);
// Note 2, Note 3
   pSa = SafeArrayCreateVector(VT_VARIANT, 1, 4);
   if (!pSa) _com_issue_error(E_OUTOFMEMORY);
 
// 为第三个元素赋值 TABLE_NAME( 索引值 2).
   ix[0] = 2;     
   TESTHR(SafeArrayPutElement(pSa, ix, &vtTableName));
 
// 由于 Variant 没有 SafeArray 的构造函数,所以手工设置 Variant 的数据类型和值。
   vtCriteria.vt = VT_ARRAY | VT_VARIANT;
   vtCriteria.parray = pSa;
 
   pRs = pCn->OpenSchema(adSchemaColumns, vtCriteria, vtMissing);
 
   long limit = pRs->GetFields()->Count;
   for (long x = 0; x < limit; x++)
      printf("%d: %s/n", x+1,
         ((char*) pRs->GetFields()->Item[x]->Name));
// Note 4
   pRs->Close();
   pCn->Close();
   }
   catch (_com_error &e)
   {
   printf("Error:/n");
   printf("Code = %08lx/n", e.Error());
   printf("Code meaning = %s/n", (char*) e.ErrorMessage());
   printf("Source = %s/n", (char*) e.Source());
   printf("Description = %s/n", (char*) e.Description());
   }
   CoUninitialize();
}
 
使用属性的 Get/Put/PutRef
VB 中,属性的名称并未被检验,无论它是被读取、被赋值,或者赋予一个引用。
Public Sub GetPutPutRef
Dim rs As New ADODB.Recordset
Dim cn As New ADODB.Connection
Dim sz as Integer
cn.Open "Provider=sqloledb;Data Source=yourserver;" & _
         "Initial Catalog=pubs;User Id=sa;Password=;"
rs.PageSize = 10
sz = rs.PageSize
rs.ActiveConnection = cn
rs.Open "authors",,adOpenStatic
´ ...
rs.Close
cn.Close
End Sub
 
以下是 VC++ 关于 Get/Put/PutRefProperty 的演示
1 .这个例子演示了省略字符串参数的两种形式:一种是采用常量 strMissing ,另一种则是由编译器自动生成一个临时的存在于 Open 方法使用期间的 _bstr_t
2 .因为操作数已经是 (IDispatch *) 的指针,所以没有必要将 rs->PutRefActiveConnection(cn) 的操作数再进行类型转换。
#import "c:/Program Files/Common Files/System/ADO/msado15.dll" /
   no_namespace rename("EOF", "EndOfFile")
#include <stdio.h>
 
void main(void)
{
   CoInitialize(NULL);
   try
   {
      _ConnectionPtr  cn("ADODB.Connection");
      _RecordsetPtr   rs("ADODB.Recordset");
      _bstr_t         strMissing(L"");
      long            oldPgSz = 0,
                      newPgSz = 5;
 
// Note 1
      cn->Open("Provider=sqloledb;Data Source=a-tima10;"
         "Initial Catalog=pubs;User Id=sa;Password=;",
         strMissing, "",
         adConnectUnspecified);
  
      oldPgSz = rs->GetPageSize();
   // -or-
      oldPgSz = rs->PageSize;
 
      rs->PutPageSize(newPgSz);
   // -or-
      rs->PageSize = newPgSz;
 
// Note 2
      rs->PutRefActiveConnection( cn );
      rs->Open("authors", vtMissing, adOpenStatic, adLockReadOnly,
               adCmdTable);
      printf("Original pagesize = %d, new pagesize = %d/n", oldPgSz,
               rs->GetPageSize());
      rs->Close();
      cn->Close();
   }
   catch (_com_error &e)
   {
      printf("Description = %s/n", (char*) e.Description());
   }
   ::CoUninitialize();
}
 
使用 GetItem(x) Item[x]
下面是 VB 中关于 Item() 的标准与交互语法的演示。
Public Sub GetItemItem
Dim rs As New ADODB.Recordset
Dim name as String
rs = rs.Open "authors", "DSN=pubs;", adOpenDynamic, _
         adLockBatchOptimistic, adTable
name = rs(0)
´ -or-
name = rs.Fields.Item(0)
rs(0) = "Test"
rs.UpdateBatch
´ Restore name
rs(0) = name
rs.UpdateBatch
rs.Close
End Sub
 
以下则是 VC++ 关于 Item 的演示
当访问 collection 中的 Item 时,索引值 2 必须被转换为 long 类型以确保正确的构造函数被调用。
#import "c:/Program Files/Common Files/System/ADO/msado15.dll" /
   no_namespace rename("EOF", "EndOfFile")
#include <stdio.h>
 
void main(void)
{
   CoInitialize(NULL);
   try {
      _RecordsetPtr   rs("ADODB.Recordset");
      _variant_t      vtFirstName;
 
      rs->Open("authors",
               "Provider=sqloledb;Data Source=a-tima10;"
               "Initial Catalog=pubs;User Id=sa;Password=;",
               adOpenStatic, adLockOptimistic, adCmdTable);
      rs->MoveFirst();
 
// Note 1. 取得一个字段的名称
      vtFirstName = rs->Fields->GetItem((long)2)->GetValue();
   // -or-
      vtFirstName = rs->Fields->Item[(long)2]->Value;
 
      printf( "First name = ´ %s ´ /n", (char*) ((_bstr_t) vtFirstName));
 
      rs->Fields->GetItem((long)2)->Value = L"TEST";
      rs->Update(vtMissing, vtMissing);
 
   // 恢复原名称
      rs->Fields->GetItem((long)2)->PutValue(vtFirstName);
      // -or-
      rs->Fields->GetItem((long)2)->Value = vtFirstName;
      rs->Update(vtMissing, vtMissing);
      rs->Close();
   }
   catch (_com_error &e)
   {
      printf("Description = ´ %s ´ /n", (char*) e.Description());
   }
   ::CoUninitialize();
}
 
利用 (IDispatch *) 转换 ADO 对象的指针类型
1 .在一个 Variant 中显式地封装一个活动的 Connection 对象,然后用 (IDispatch *) 进行类型转换确保正确的构造函数被调用。同时明确地设置第二个参数为缺省的 true ,使该对象的引用计数在 Recordset::Open 操作完成后仍得到正确的维护。
2 .表达式 (_bstr_t) 不是一个类型转换,而是一个 _variant_t 的操作符,用以从中提取一个 _bstr_t 字符串。
表达式 (char*) 也不是一个类型转换,而是一个 _bstr_t 的操作符,用以从中提取封装在 _bstr_t 中的字符串的指针。
下面这些代码演示了 _variant_t _bstr_t 的一些常见操作。
#import "c:/Program Files/Common Files/System/ADO/msado15.dll" /
no_namespace rename("EOF", "EndOfFile")
 
#include <stdio.h>
 
void main(void)
{
   CoInitialize(NULL);
   try
   {
      _ConnectionPtr pConn("ADODB.Connection");
      _RecordsetPtr  pRst("ADODB.Recordset");
 
      pConn->Open("Provider=sqloledb;Data Source=a-tima10;"
         "Initial Catalog=pubs;User Id=sa;Password=;",
         "", "", adConnectUnspecified);
// Note 1
      pRst->Open(
         "authors",
         _variant_t((IDispatch *) pConn, true),
         adOpenStatic,
         adLockReadOnly,
         adCmdTable);
      pRst->MoveLast();
// Note 2
      printf("Last name is ´ %s %s ´ /n",
            (char*) ((_bstr_t) pRst->GetFields()->GetItem("au_fname")->GetValue()),
            (char*) ((_bstr_t) pRst->Fields->Item["au_lname"]->Value));
 
      pRst->Close();
      pConn->Close();
   }
   catch (_com_error &e)
   {
      printf("Description = ´ %s ´ /n", (char*) e.Description());
   }  
::CoUninitialize();
}
///
VC++ ADO 的扩展
///
对于 VC++ 程序员而言,每次都要将 ADO 返回的数据转换成一般的 C++ 数据类型,接着将数据存入一个类或结构总是一件枯燥的事。更讨厌的是这也带来了效率的低下。
因此, ADO 提供了一个接口以支持将数据直接返回为一个本地化的 C/C++ 数据类型而非 VARIANT ,并提供了一系列的预处理宏来方便使用这些接口。这样做的结果是一个复杂的工具可以很轻松的被使用并能获得很好的性能。
一个普通的 C/C++ 客户场景是将一个 Recordset 中的一条记录绑定到一个包含本地 C/C++ 数据类型的 C/C++ 结构或类之上。如果通过 Variant 传递数据,这意味着要编写大量的转换代码,以在 VARIANT C/C++ 本地类型间进行数据转换。 VC++ ADO 的扩展出现的目的就是要简化这一过程。
 
如何使用 VC++ ADO 的扩展
 
IADORecordBinding 接口
VC++ ADO 的扩展联系或绑定了一个 Recordset 对象的各个字段到 C/C++ 变量。当被绑定的 Recordset 的当前行改变时,其中所有被绑定的字段的值也同样会被拷贝到相应的 C/C++ 变量中。如果需要,被拷贝的数据还会自动进行相应的数据类型转换。
IADORecordBinding 接口的 BindToRecordset 方法将字段绑定到 C/C++ 变量之上。 AddNew 方法则是增加一个新的行到被绑定的 Recordset Update 方法利用 C/C++ 变量的值填充 Recordset 中新的行或更新已存在的行。
IADORecordBinding 接口由 Recordset 对象实现,你不需要自己编码进行实现。
 
绑定条目
VC++ ADO 的扩展在一个 Recordset 对象与一个 C/C++ 变量间进行映像( Map )。一个字段与对应的一个变量间的映像被称作一个绑定条目。预定义的宏为数字、定长或不定长数据提供了绑定条目。所有的绑定条目与相应的 C/C++ 变量都被封装、声明在一个从 VC++ 扩展类 CADORecordBinding 派生的类中。这个 CADORecordBinding 类在内部由绑定条目宏定义。
ADO 内部,将所有宏的参数都映射在一个 OLE DB DBBINDING 结构中,并创建一个 OLE DB 访问子( Accessor )对象来管理所有的行为和字段与变量间的数据转换。 OLE DB 定义的数据由以下三部分组成:存储数据的缓冲区;一个状态值表示一个字段是否被成功地被存入缓冲区,或变量值是否被成功地存入字段;数据长度。(参见 OLE DB 程序员参考第 6 章:读写数据的更多信息)
 
所需的头文件
为了使用 VC++ ADO 的扩展,你得在你的应用中包含这个头文件: #include <icrsint.h>
 
绑定 Recordset 的字段
要绑定 Recordset 的字段到 C/C++ 变量,需要这样做:
1 .创建一个 CADORecordsetBinding 的派生类。
2 .在派生类中定义绑定条目和相应的 C/C++ 变量。注意不要使用逗号、分号切断宏。每个宏都会自动地定义适当的分隔符。
为每个被映像的字段定义一个绑定条目。并注意根据不同情况选用 ADO_FIXED_LENGTH_ENTRY ADO_NUMERIC_ENTRY ADO_VARIABLE_LENGTH_ENTRY 中的某个宏。
3 .在你的应用中,创建一个该派生类的实例。从 Recordset 中获得 IADORecordBinding 接口,然后调用 BindToRecordset 方法将 Recordset 的所有字段绑定到对应的 C/C++ 变量之上。
请参见示例程序以获得更多信息。
 
接口方法
IADORecordBinding 接口只有三个方法: BindToRecordset, AddNew, Update 。每个方法所需的唯一的参数就是一个 CADORecordBinding 派生类的实例指针。因此, AddNew Update 方法不能使用任何与它们同名的 ADO 方法中的参数。
 
语法
BindToRecordset 方法将字段绑定到 C/C++ 变量之上。
BindToRecordset(CADORecordBinding *binding)
AddNew 方法则引用了它的同名 ADO 函数,来增加一个新的记录行。
AddNew(CADORecordBinding *binding)
Update 方法也引用了它的同名 ADO 函数,来更新 Recordset
Update(CADORecordBinding *binding)
 
绑定条目宏
绑定条目宏定义了一个 Recordset 字段与一个变量间的对应关系。每个条目的绑定宏由开始宏与结束宏组成并配对使用。
定长数据的宏适用于 adDate adBoolean 等,数字的宏适用于 adTinyInt, adInteger adDouble 等,变长数据的宏适用于 adChar, adVarChar adVarBinary 等。所有的数字类型,除了 adVarNumeric 以外也是定长数据类型。每个宏的族之间都有不同的参数组,因此你可以排除不感兴趣的绑定信息。
参见 OLE DB 程序员参考附录 A :数据类型的更多信息
 
开始绑定条目
BEGIN_ADO_BINDING(Class)
 
定长数据:
ADO_FIXED_LENGTH_ENTRY(Ordinal, DataType, Buffer, Status, Modify)
ADO_FIXED_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Modify)
数字型数据:
ADO_NUMERIC_ENTRY(Ordinal, DataType, Buffer, Precision, Scale, Status,                                   Modify)
ADO_NUMERIC_ENTRY2(Ordinal, DataType, Buffer, Precision, Scale, Modify)
变长数据:
ADO_VARIABLE_LENGTH_ENTRY(Ordinal, DataType, Buffer, Size, Status,                                                       Length, Modify)
ADO_VARIABLE_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Size, Status,                                                       Modify)
ADO_VARIABLE_LENGTH_ENTRY3(Ordinal, DataType, Buffer, Size, Length,                                                       Modify)
ADO_VARIABLE_LENGTH_ENTRY4(Ordinal, DataType, Buffer, Size, Modify)
 
结束绑定
END_ADO_BINDING()
参数
描述
Class
派生类的名字。
Ordinal
1 开始的序号,对应于 Recordset 中的字段。
DataType
C/C++ 变量对应的 ADO 数据类型 ( 参见 DataTypeEnum 以获得有效数据类型的列表 ) 。如果需要,字段的值会被转换成该类型的值。
Buffer
对应的 C/C++ 变量的名字。
Size
C/C++ 变量的最大字节数。如果是个变长字符串,使用 0 表示即可。
Status
指示变量的名字。该变量用以表示缓冲是否有效,数据转换是否成功。
adFldOK 意味着转换成功; adFldNull 意味着该字段的值为空。其他可能的值见后面的状态值列表。
Modify
逻辑标志。 TRUE 意味着 ADO 允许利用变量值更新 Recordset 中的字段的值。
设置该值为 TRUE 将允许更新,如果你只想检查字段的值而不想改变它那么就设置为 FALSE
Precision
数字型变量的位数。
Scale
数字型变量的小数位数。
Length
一个 4 字节变量的名字。该变量将包含缓冲区中数据的实际长度。
 
状态值
变量 Status 的值指示了一个字段的值是否被成功的拷贝到了对应的变量中。写数据时,可以给 Status 赋值为 adFldNull 来指示该字段将被设置为 null
常量
描述
adFldOK
0
一个非空的字段值被返回。
adFldBadAccessor
1
绑定无效。
adFldCantConvertValue
2
值因为符号不匹配或超界外的原因导致无法被正确转换。
adFldNull
3
读字段值时,指示一个空值被返回。写字段值时,指示当字段自身无法编码 NULL 时该字段将被设置为 NULL
adFldTruncated
4
变长数据或数字被截断。
adFldSignMismatch
5
值是有符号数,而数据类型是无符号数。
adFldDataOverFlow
6
数据值超出界限。
adFldCantCreate
7
不知名的列类型和字段已经被打开。
adFldUnavailable
8
字段值无法确定。比如一个新的未赋值的无缺省值的字段。
adFldPermissionDenied
9
未被允许更新数据。
adFldIntegrityViolation
10
更新字段时值违反了列的完整性要求。
adFldSchemaViolation
11
更新字段时值违反了列的规范要求。
adFldBadStatus
12
更新字段时,无效的状态参数。
adFldDefault
13
更新字段时,使用缺省值。
 
使用 VC++ ADO 的扩展的示例
在这个例子中,还使用了 COM 专有的“智能指针”功能,它能自动处理 IADORecordBinding 接口的 QueryInterface 和引用计数。如果没有智能指针,你得这样编码:
IADORecordBinding   *picRs = NULL;
...
TESTHR(pRs->QueryInterface(
          __uuidof(IADORecordBinding), (LPVOID*)&picRs));
...
if (picRs) picRs->Release();
使用智能指针,你可以用这样的语句从 IADORecordBinding 接口派生 IADORecordBindingPtr 类型:
_COM_SMARTPTR_TYPEDEF(IADORecordBinding, __uuidof(IADORecordBinding));
然后这样实例化指针:
IADORecordBindingPtr picRs(pRs);
因为 VC++ 的扩展由 Recordset 对象实现,因此智能指针 picRs 的构造函数使用了 _RecordsetPtr 类指针 pRs 。构造函数利用 pRs 调用 QueryInterface 来获得 IADORecordBinding 接口。
 
// 以下即是示例程序
#import "c:/Program Files/Common Files/System/ADO/msado15.dll" /
   no_namespace rename("EOF", "EndOfFile")
 
#include <stdio.h>
#include <icrsint.h>
_COM_SMARTPTR_TYPEDEF(IADORecordBinding, __uuidof(IADORecordBinding));
 
inline void TESTHR(HRESULT _hr) { if FAILED(_hr) _com_issue_error(_hr); }
 
class CCustomRs : public CADORecordBinding
{
BEGIN_ADO_BINDING(CCustomRs)
   ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_ch_fname,
                        sizeof(m_ch_fname), m_ul_fnameStatus, false)
   ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_ch_lname,
                        sizeof(m_ch_lname), m_ul_lnameStatus, false)
END_ADO_BINDING()
public:
   CHAR    m_ch_fname[22];
   CHAR    m_ch_lname[32];
   ULONG   m_ul_fnameStatus;
   ULONG   m_ul_lnameStatus;
};
 
void main(void)
{
   ::CoInitialize(NULL);
   try
      {
      _RecordsetPtr pRs("ADODB.Recordset");
      CCustomRs rs;
      IADORecordBindingPtr picRs(pRs);
     
      pRs->Open("SELECT * FROM Employee ORDER BY lname",
         "dsn=pubs;uid=sa;pwd=;",
         adOpenStatic, adLockOptimistic, adCmdText);
     
      TESTHR(picRs->BindToRecordset(&rs));
 
      while (!pRs->EndOfFile)
         {
      // 处理 CCustomRs 中的数据
         printf("Name = %s %s/n",
            (rs.m_ul_fnameStatus == adFldOK ? rs.m_ch_fname: "<Error>"),
            (rs.m_ul_lnameStatus == adFldOK ? rs.m_ch_lname: "<Error>"));
 
      // 移动到下一行,新行的值会被自动填充到对应的 CCustomRs 的变量中
         pRs->MoveNext();
         }
      }
   catch (_com_error &e )
      {
      printf("Error:/n");
      printf("Code = %08lx/n", e.Error());
      printf("Meaning = %s/n", e.ErrorMessage());
      printf("Source = %s/n", (LPCSTR) e.Source());
      printf("Description = %s/n", (LPCSTR) e.Description());
      }
   ::CoUninitialize();
}
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值