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();
}
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();
}
本栏文章均来自于互联网,版权归原作者和各发布网站所有,本站收集这些文章仅供学习参考之用。任何人都不能将这些文章用于商业或者其他目的。( Pfan.cn )
【编程爱好者论坛】