【导读】本文是对于
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();
}