[转载]VC++程序员应当如何阅读ADO文档
生活随笔
收集整理的這篇文章主要介紹了
[转载]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();
}
轉載于:https://www.cnblogs.com/smartstone/archive/2006/08/31/491325.html
總結
以上是生活随笔為你收集整理的[转载]VC++程序员应当如何阅读ADO文档的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 富途控股、老虎证券拟下架中国境内APP
- 下一篇: 谷歌 Gboard 输入法 12.9.2