《ADO API參考》用VB的語法描述了ADO API的內(nèi)容。但ADO程序員卻使用著不同的編程語言,比如VB,VC++,VJ++。對此《ADO for VC++的語法索引》提供了符合VC++語法規(guī)范的詳細描述,包括功能、參數(shù)、異常處理等等。
ADO基于若干的COM借口實現(xiàn),因此它的使用對于一個正進行COM編程的程序員而言更簡單。比如,幾乎所有使用COM的細節(jié)對于VB程序員而言都是隱藏了的,但對于VC++程序員而言卻要特別注意。以下是對于C和C++程序員使用ADO和#import指示符方面的概述,主要描述了COM使用的數(shù)據(jù)類型(Variant, BSTR, and SafeArray)和異常的處理(_com_error)。
使用#import編譯指示符
#import編譯指示符使使用ADO的方法與屬性簡單化。這個指示符需要一個類型庫文件名,比如ADO.dll(Msado15.dll),并生成對應的頭文件,其中包括定義的類型、接口的智能化指針、常量。并且所有的接口都被封裝成類。
對于類中的每個操作(或稱方法、屬性調(diào)用),都有一個聲明以保證能直接調(diào)用它(或稱作操作的源形式),以及另一個聲明來調(diào)用這個源操作并在操作失敗時拋出一個COM錯誤。如果操作是一個屬性,那么編譯指示符可以為該操作創(chuàng)建一個可交互的類似VB的語法形式。
返回/設(shè)置屬性的操作有對應的形式化的名字—GetProperty/PutPropert,而設(shè)置一個指向某個ADO對象的指針型屬性值時則是PutRefProperty。你將使用如下的形式讀寫屬性的值:
variable = objectPtr->GetProperty(); // 讀取屬性的值
objectPtr->PutProperty(value); // 設(shè)置屬性的值
objectPtr->PutRefProperty(&value); // 設(shè)置一個指針型的屬性的值
直接使用屬性
__declspec(property...)編譯指示符是微軟定義的一個針對C語言的擴展,使一個函數(shù)象一個屬性那樣被使用。這樣你就可以采用如下的語法形式象在使用VB一樣讀寫一個屬性的值: objectPtr->property = value; // 設(shè)置屬性的值
variable = objectPtr->property; // 讀取屬性的值
__declspec(property...)編譯指示符只能針對屬性的讀寫函數(shù)使用,并根據(jù)屬性是否可供讀寫自動生成對應的調(diào)用形式。每個屬性可能有GetProperty, PutProperty,PutRefProperty三個函數(shù),但這個編譯符只能生成其中的兩種交互形式。比如,Command對象的ActiveConnection屬性有GetActiveConnection和PutRefActiveConnection這兩個讀寫函數(shù)。而PutRef-的形式在實踐中是個好的選擇,你可以將一個活動的Connection對象的指針保存在這個屬性中。另一方面,Recordset對象則有Get-, Put-, and PutRefActiveConnection操作,但卻沒有可交互的語法形式。
Collections,GetItem方法和Item屬性
ADO定義了幾種集合Collection,包括Fields,Parameters,Properties,和Errors。在Visual C++中,GetItem(index)方法返回Collection中的某個成員。Index是一個Variant型的參數(shù),內(nèi)容可以是一個該成員對應的序數(shù),也可以是一個包括其名稱的字符串。
__declspec(property...)編譯指示符為Item屬性生成對應于GetItem()方法的直接使用形式(上文提到的可交互的語法形式)。這種形式類似于引用數(shù)組元素時使用[]的語法形式:
collectionPtr->GetItem(index);
collectionPtr->Item[index];
舉例說明,要給一個Recordset對象rs中的某個字段賦值,而這個Recordset對象派生于pubs數(shù)據(jù)庫中的authors表。使用Item()屬性訪問這個Recordset的Fields集合中的第三個字段(集合總是從0開始編號,假設(shè)第三個字段名為au_fname)。然后調(diào)用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特定的數(shù)據(jù)類型
一般的,你在《ADO API Reference》中看到的VB的數(shù)據(jù)類型在VC++中也能找到對應的類型。其中包括標準的數(shù)據(jù)類型,比如unsigned char對應VB的Byte,short對應Integer,long對應Long。參見《Syntax Indexes》將可以獲得關(guān)于所需操作數(shù)的更詳細內(nèi)容。
而作為例外的專屬于COM使用的數(shù)據(jù)類型則有:Variant, BSTR, and SafeArray.
Variant
Variant是一個結(jié)構(gòu)化的數(shù)據(jù)類型,包含了一個成員值及其數(shù)據(jù)類型的表示。Variant可以表示相當多的數(shù)據(jù)類型,甚至另一個Variant, BSTR, Boolean, Idispatch或Iunknown指針,貨幣,日期等等。同時COM也提供了許多方法使數(shù)據(jù)類型間的轉(zhuǎn)換更簡單化。
_variant_t類封裝并管理Variant這一數(shù)據(jù)類型。
當《ADO API Reference》中說到一個方法或?qū)傩砸褂靡粋參數(shù)時,通常意味著需要一個_variant_t類型的參數(shù)。這條準則在《ADO API Reference》的Parameters一章中得到了明白無誤的表述。作為例外的是,有時則會要求操作數(shù)是一個標準的數(shù)據(jù)類型,比如Long或Byte, 或者一個枚舉值。另一個例外是要求操作數(shù)是一個字符串String。
BSTR
BSTR (Basic STRing)也是一個結(jié)構(gòu)化的數(shù)據(jù)類型,包括了串及串的長度。COM提供了方法進行串的空間分配、操作、釋放。
_bstr_t類封裝并管理BSTR這一數(shù)據(jù)類型。
當《ADO API Reference》中說到一個方法或?qū)傩砸褂靡粋字符串參數(shù)時,通常意味著需要一個類_bstr_t型的參數(shù)。
_variant_t和_bstr_t類的強制類型轉(zhuǎn)換
通常當傳遞一個_variant_t或_bstr_t參數(shù)給一個操作時并不需要顯式的類型轉(zhuǎn)換代碼。如果_variant_t或_bstr_t類提供了對應于該參數(shù)類型的構(gòu)造函數(shù),那么編譯器將會自動生成適當?shù)腳variant_t或_bstr_t值。
然而,當參數(shù)模棱兩可時,即對應了多個構(gòu)造函數(shù)時,你就必須顯式地調(diào)用正確的構(gòu)造函數(shù)以獲得正確的參數(shù)。比如,Recordset::Open方法的函數(shù)聲明如下:
HRESULT Open (
const _variant_t & Source,
const _variant_t & ActiveConnection,
enum CursorTypeEnum CursorType,
enum LockTypeEnum LockType,
long Options );
其中參數(shù)ActiveConnection就是針對一個variant_t型變量的引用,它可以是一個連接串或者一個指向已打開的Connection對象的指針。
正確的_variant_t型參數(shù)會被構(gòu)造,無論你傳遞的是一個類似"DSN=pubs;uid=sa;pwd=;"這樣的字符串,或者是一個類似"(IDispatch *) pConn"的指針。
或者你還可以顯式的編寫"_variant_t((IDispatch *) pConn, true)"這樣的代碼來傳遞一個包含指針的_variant_t變量。這里的強制類型轉(zhuǎn)換(IDispatch *)避免了可能調(diào)用IUnknown接口構(gòu)造函數(shù)的模棱兩可性。
雖然很少提及但特別重要的是,ADO總是一個IDispatch接口。任何被傳遞的被包含在Variant中的指針都必須被轉(zhuǎn)換為一個IDispatch接口指針。
最后需要說明的是構(gòu)造函數(shù)的第二個邏輯參數(shù)是可選擇的,它的缺省值是True。這個參數(shù)將決定Variant的構(gòu)造函數(shù)是否調(diào)用內(nèi)嵌的AddRef()方法,并在完成ADO的方法或?qū)傩哉{(diào)用后是否自動調(diào)用_variant_t::Release()方法
SafeArray
SafeArray也是一種結(jié)構(gòu)化的數(shù)據(jù)類型,包含了一個由其它數(shù)據(jù)類型的數(shù)據(jù)元素組成的數(shù)組。之所以稱之為安全的數(shù)組是因為它包含了每一維的邊界信息,并限制在邊界內(nèi)進行數(shù)組元素的訪問。
當《ADO API Reference》中說到一個方法或?qū)傩砸褂没蛘叻祷匾粋數(shù)組時,通常意味著是一個SafeArray數(shù)組,而非一個本地化的C/C++數(shù)組。
比如,Connection對象的OpenSchema方法的第二個參數(shù)需要一個由Variant值組成的數(shù)組。這些Variant值必須作為一個SafeArray數(shù)組的元素進行傳遞。而這個SafeArray數(shù)組本身又被作為一個Variant進行傳遞。
更進一步的,F(xiàn)ind方法的第一個參數(shù)是一個指向一維SafeArray數(shù)組的Variant;AddNew方法的可選的第一與第二個參數(shù)也是一個一維的SafeArray數(shù)組;GetRows方法的返回值則是一個包含二維SafeArray數(shù)組的Variant。
缺省參數(shù)
VB允許省略方法的某些參數(shù)。例如,Recordset對象的Open方法有五個參數(shù),但是你可以跳過中間的參數(shù)并省略之后的參數(shù)。被省略的參數(shù)會被自動創(chuàng)建的BSTR或Variant缺省值替代。
在C/C++中,所有的操作數(shù)必須被明確。如果你想定義一個字符串型的缺省參數(shù),那么就定義一個包含空字符串的_bstr_t。如果想定義一個Variant型的缺省參數(shù),那么就定義一個值為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
參數(shù)RecordsAffected與Parameters都是指向Variant的指針。Parameters是一個傳入?yún)?shù),指向一個包含一個或一組參數(shù)信息的Variant的地址,將決定命令執(zhí)行的內(nèi)容。RecordsAffected是一個傳出參數(shù),指向一個包含該方法返回時影響行的數(shù)目的Variant的地址。
在Command對象的Execute方法中,如果只是沒有參數(shù)的話,需要將Parameters設(shè)置為&vtMissing (推薦使用)或者一個空指針(NULL)。如果傳遞的是一個空指針,那么等價的vtMissing會被傳遞并完成操作。
在所有的方法中,通過設(shè)置RecordsAffected為空指針可以指示不需返回被影響的記錄的數(shù)目。此時,這個空指針實際成為了指示該方法拋棄被影響記錄數(shù)目的指示器。
因此,如下的編碼是有效的:
pConnection->Execute("commandText", NULL, adCmdText);
pCommand->Execute(NULL, NULL, adCmdText);
pRecordset->NextRecordset(NULL);
錯誤的處理
在COM中,大多數(shù)的操作總是返回一個HRESULT值說明該函數(shù)是否被成功完成。編譯指示符#import為所有源方法和屬性提供了封裝好的代碼并檢查返回的HRESULT值。如果HRESULT指示失敗,這些封裝代碼將會通過調(diào)用以HRESULT為參數(shù)的_com_issue_errorex()拋出一個COM錯誤。COM錯誤對象將在try-catch塊中被捕獲(出于效率的考慮,實際捕獲的是一個_com_error對象的引用指針)。
記住,由ADO操作失敗產(chǎn)生的錯誤才是ADO錯誤。由下層提供者返回的錯誤以Connection對象中Errors集合中的一個Error對象的形式出現(xiàn)。
編譯指示符#import只能為在ADO.dll中聲明的方法和屬性提供錯誤處理例程。因此,你可以基于同樣的錯誤處理機制編寫自己的錯誤檢查宏或內(nèi)置函數(shù)。參見《Visual C++擴展》以及本文后續(xù)的示例代碼。
在VC++與VB中編碼時的約定
下面是ADO文檔中關(guān)于如何使用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對象的指針變量的數(shù)據(jù)類型為_RecordsetPtr,并如下聲明:
_RecordsetPtr rs;
而一個_Recordset對象的實例則如下聲明:
_RecordsetPtr rs("ADODB.Recordset");
或者:
_RecordsetPtr rs;
rs.CreateInstance("ADODB.Recordset");
或者:
_RecordsetPtr rs;
rs.CreateInstance(__uuidof(_Recordset));
當CreateInstance方法被成功調(diào)用后,該變量可被如此使用:rs->Open(...);
注意,如果變量是一個類的實例則用"."操作符,若是一個指向?qū)嵗闹羔槃t應使用"->"操作符。
一個變量能通過兩種方式被使用。因為"->"操作符被重載,允許一個對象實例類似一個接口指針那樣被使用;"->"操作符返回該指針;而由這個返回的指針訪問_Recordset對象的成員。
編寫省略String參數(shù)的代碼
當你需要利用VB編寫省略String參數(shù)的代碼時,只需簡單的略掉該操作數(shù)即可。但在VC++中,你必須指定該操作數(shù)為一個包含空字符串的_bstr_t變量:_bstr_t strMissing(L"");
編寫省略Variant參數(shù)的代碼
當你需要利用VB編寫省略Variant參數(shù)的代碼時,只需簡單的略掉該操作數(shù)即可。但在VC++中,你必須指定所有的操作數(shù)。編寫省略Variant參數(shù)的代碼只需將該Variant設(shè)為專門的值,可以定義一個值為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數(shù)組
在VB中,利用Dim語句可以進行Variant數(shù)組的編程,并可以使用Array的函數(shù)。見如下示例:
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數(shù)組。注意注釋對應了編碼的步驟。
1.再一次的,TESTHR()內(nèi)置函數(shù)被定義以利用預存的錯誤處理機制。
2.如果你只需要一個一維數(shù)組,你可以使用SafeArrayCreateVector,而非SAFEARRAYBOUND聲明與SafeArrayCreate函數(shù)。下面的代碼使用了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四列相聯(lián)系。為此,一個有四個Variant元素的數(shù)組被創(chuàng)建。而對應于第三列TABLE_NAME的值被設(shè)置。
由若干列組成的返回的Recordset只是對應的所有列的一個子集,并且每一行的值保持了一一對應。
4.熟悉SafeArrays的人也許會對退出前沒有調(diào)用SafeArrayDestroy()感到驚奇。實際上,在這種情況下調(diào)用SafeArrayDestroy()會導致一個運行時的異常發(fā)生。這是因為vtCriteria的析構(gòu)函數(shù)會在_variant_t超出使用范圍時調(diào)用VariantClear(),從而釋放SafeArray。只調(diào)用SafeArrayDestroy,而沒有手動清除_variant_t,將會導致析構(gòu)函數(shù)試圖去清除一個無效的SafeArray指針。如果要調(diào)用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的構(gòu)造函數(shù),所以手工設(shè)置Variant的數(shù)據(jù)類型和值。
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++關(guān)于Get/Put/PutRefProperty的演示
1.這個例子演示了省略字符串參數(shù)的兩種形式:一種是采用常量strMissing,另一種則是由編譯器自動生成一個臨時的存在于Open方法使用期間的_bstr_t。
2.因為操作數(shù)已經(jīng)是(IDispatch *)的指針,所以沒有必要將rs->PutRefActiveConnection(cn)的操作數(shù)再進行類型轉(zhuǎn)換。
#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中關(guān)于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++關(guān)于Item的演示
當訪問collection中的Item時,索引值2必須被轉(zhuǎn)換為long類型以確保正確的構(gòu)造函數(shù)被調(diào)用。
#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 *)轉(zhuǎn)換ADO對象的指針類型
1.在一個Variant中顯式地封裝一個活動的Connection對象,然后用(IDispatch *)進行類型轉(zhuǎn)換確保正確的構(gòu)造函數(shù)被調(diào)用。同時明確地設(shè)置第二個參數(shù)為缺省的true,使該對象的引用計數(shù)在Recordset::Open操作完成后仍得到正確的維護。
2.表達式(_bstr_t)不是一個類型轉(zhuǎn)換,而是一個_variant_t的操作符,用以從中提取一個_bstr_t字符串。
表達式(char*)也不是一個類型轉(zhuǎn)換,而是一個_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返回的數(shù)據(jù)轉(zhuǎn)換成一般的C++數(shù)據(jù)類型,接著將數(shù)據(jù)存入一個類或結(jié)構(gòu)總是一件枯燥的事。更討厭的是這也帶來了效率的低下。
因此,ADO提供了一個接口以支持將數(shù)據(jù)直接返回為一個本地化的C/C++數(shù)據(jù)類型而非VARIANT,并提供了一系列的預處理宏來方便使用這些接口。這樣做的結(jié)果是一個復雜的工具可以很輕松的被使用并能獲得很好的性能。
一個普通的C/C++客戶場景是將一個Recordset中的一條記錄綁定到一個包含本地C/C++數(shù)據(jù)類型的C/C++結(jié)構(gòu)或類之上。如果通過Variant傳遞數(shù)據(jù),這意味著要編寫大量的轉(zhuǎn)換代碼,以在VARIANT和C/C++本地類型間進行數(shù)據(jù)轉(zhuǎn)換。VC++對ADO的擴展出現(xiàn)的目的就是要簡化這一過程。
如何使用VC++對ADO的擴展
IADORecordBinding接口
VC++對ADO的擴展聯(lián)系或綁定了一個Recordset對象的各個字段到C/C++變量。當被綁定的Recordset的當前行改變時,其中所有被綁定的字段的值也同樣會被拷貝到相應的C/C++變量中。如果需要,被拷貝的數(shù)據(jù)還會自動進行相應的數(shù)據(jù)類型轉(zhuǎn)換。
IADORecordBinding接口的BindToRecordset方法將字段綁定到C/C++變量之上。AddNew方法則是增加一個新的行到被綁定的Recordset。Update方法利用C/C++變量的值填充Recordset中新的行或更新已存在的行。
IADORecordBinding接口由Recordset對象實現(xiàn),你不需要自己編碼進行實現(xiàn)。
綁定條目
VC++對ADO的擴展在一個Recordset對象與一個C/C++變量間進行映像(Map)。一個字段與對應的一個變量間的映像被稱作一個綁定條目。預定義的宏為數(shù)字、定長或不定長數(shù)據(jù)提供了綁定條目。所有的綁定條目與相應的C/C++變量都被封裝、聲明在一個從VC++擴展類CADORecordBinding派生的類中。這個CADORecordBinding類在內(nèi)部由綁定條目宏定義。
在ADO內(nèi)部,將所有宏的參數(shù)都映射在一個OLE DB DBBINDING結(jié)構(gòu)中,并創(chuàng)建一個OLE DB訪問子(Accessor)對象來管理所有的行為和字段與變量間的數(shù)據(jù)轉(zhuǎn)換。OLE DB定義的數(shù)據(jù)由以下三部分組成:存儲數(shù)據(jù)的緩沖區(qū);一個狀態(tài)值表示一個字段是否被成功地被存入緩沖區(qū),或變量值是否被成功地存入字段;數(shù)據(jù)長度。(參見OLE DB程序員參考第6章:讀寫數(shù)據(jù)的更多信息)
所需的頭文件
為了使用VC++對ADO的擴展,你得在你的應用中包含這個頭文件:#include <icrsint.h>
綁定Recordset的字段
要綁定Recordset的字段到C/C++變量,需要這樣做:
1.創(chuàng)建一個CADORecordsetBinding的派生類。
2.在派生類中定義綁定條目和相應的C/C++變量。注意不要使用逗號、分號切斷宏。每個宏都會自動地定義適當?shù)姆指舴?br> 為每個被映像的字段定義一個綁定條目。并注意根據(jù)不同情況選用ADO_FIXED_LENGTH_ENTRY、 ADO_NUMERIC_ENTRY、ADO_VARIABLE_LENGTH_ENTRY中的某個宏。
3.在你的應用中,創(chuàng)建一個該派生類的實例。從Recordset中獲得IADORecordBinding接口,然后調(diào)用BindToRecordset方法將Recordset的所有字段綁定到對應的C/C++變量之上。
請參見示例程序以獲得更多信息。
接口方法
IADORecordBinding接口只有三個方法:BindToRecordset, AddNew,和Update。每個方法所需的唯一的參數(shù)就是一個CADORecordBinding派生類的實例指針。因此,AddNew和Update方法不能使用任何與它們同名的ADO方法中的參數(shù)。
語法
BindToRecordset方法將字段綁定到C/C++變量之上。
BindToRecordset(CADORecordBinding *binding)
AddNew方法則引用了它的同名ADO函數(shù),來增加一個新的記錄行。
AddNew(CADORecordBinding *binding)
Update方法也引用了它的同名ADO函數(shù),來更新Recordset。
Update(CADORecordBinding *binding)
綁定條目宏
綁定條目宏定義了一個Recordset字段與一個變量間的對應關(guān)系。每個條目的綁定宏由開始宏與結(jié)束宏組成并配對使用。
定長數(shù)據(jù)的宏適用于adDate,adBoolean等,數(shù)字的宏適用于adTinyInt, adInteger和adDouble等,變長數(shù)據(jù)的宏適用于adChar, adVarChar和adVarBinary等。所有的數(shù)字類型,除了adVarNumeric以外也是定長數(shù)據(jù)類型。每個宏的族之間都有不同的參數(shù)組,因此你可以排除不感興趣的綁定信息。
參見OLE DB程序員參考附錄A:數(shù)據(jù)類型的更多信息
開始綁定條目
BEGIN_ADO_BINDING(Class)
定長數(shù)據(jù):
ADO_FIXED_LENGTH_ENTRY(Ordinal, DataType, Buffer, Status, Modify)
ADO_FIXED_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Modify)
數(shù)字型數(shù)據(jù):
ADO_NUMERIC_ENTRY(Ordinal, DataType, Buffer, Precision, Scale, Status, Modify)
ADO_NUMERIC_ENTRY2(Ordinal, DataType, Buffer, Precision, Scale, Modify)
變長數(shù)據(jù):
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)
結(jié)束綁定
END_ADO_BINDING()
參數(shù) 描述 Class 派生類的名字。 Ordinal 從1開始的序號,對應于Recordset中的字段。 DataType 與C/C++變量對應的ADO數(shù)據(jù)類型(參見DataTypeEnum以獲得有效數(shù)據(jù)類型的列表)。如果需要,字段的值會被轉(zhuǎn)換成該類型的值。 Buffer 對應的C/C++變量的名字。 Size 該C/C++變量的最大字節(jié)數(shù)。如果是個變長字符串,使用0表示即可。 Status 指示變量的名字。該變量用以表示緩沖是否有效,數(shù)據(jù)轉(zhuǎn)換是否成功。
值adFldOK意味著轉(zhuǎn)換成功;adFldNull意味著該字段的值為空。其他可能的值見后面的狀態(tài)值列表。 Modify 邏輯標志。TRUE意味著ADO允許利用變量值更新Recordset中的字段的值。
設(shè)置該值為TRUE將允許更新,如果你只想檢查字段的值而不想改變它那么就設(shè)置為FALSE。 Precision 數(shù)字型變量的位數(shù)。 Scale 數(shù)字型變量的小數(shù)位數(shù)。 Length 一個4字節(jié)變量的名字。該變量將包含緩沖區(qū)中數(shù)據(jù)的實際長度。
狀態(tài)值
變量Status的值指示了一個字段的值是否被成功的拷貝到了對應的變量中。寫數(shù)據(jù)時,可以給Status賦值為adFldNull來指示該字段將被設(shè)置為null。
常量 值 描述 adFldOK 0 一個非空的字段值被返回。 adFldBadAccessor 1 綁定無效。 adFldCantConvertValue 2 值因為符號不匹配或超界外的原因?qū)е聼o法被正確轉(zhuǎn)換。 adFldNull 3 讀字段值時,指示一個空值被返回。寫字段值時,指示當字段自身無法編碼NULL時該字段將被設(shè)置為NULL。 adFldTruncated 4 變長數(shù)據(jù)或數(shù)字被截斷。 adFldSignMismatch 5 值是有符號數(shù),而數(shù)據(jù)類型是無符號數(shù)。 adFldDataOverFlow 6 數(shù)據(jù)值超出界限。 adFldCantCreate 7 不知名的列類型和字段已經(jīng)被打開。 adFldUnavailable 8 字段值無法確定。比如一個新的未賦值的無缺省值的字段。 adFldPermissionDenied 9 未被允許更新數(shù)據(jù)。 adFldIntegrityViolation 10 更新字段時值違反了列的完整性要求。 adFldSchemaViolation 11 更新字段時值違反了列的規(guī)范要求。 adFldBadStatus 12 更新字段時,無效的狀態(tài)參數(shù)。 adFldDefault 13 更新字段時,使用缺省值。
使用VC++對ADO的擴展的示例
在這個例子中,還使用了COM專有的“智能指針”功能,它能自動處理IADORecordBinding接口的QueryInterface和引用計數(shù)。如果沒有智能指針,你得這樣編碼:
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對象實現(xiàn),因此智能指針picRs的構(gòu)造函數(shù)使用了_RecordsetPtr類指針pRs。構(gòu)造函數(shù)利用pRs調(diào)用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中的數(shù)據(jù)
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();
}
|