在開(kāi)始新的結果集對象(Rowset)之旅之前,我(wǒ)(wǒ)們再來補充一(yī)個關于Command對象的用法,在有些情況下(xià),我(wǒ)(wǒ)們執行的SQL語句隻是一(yī)個Update、Insert或Delete等操作,有些時候我(wǒ)(wǒ)們可能直接執行就是一(yī)個存儲過程而已,存儲過程可能産生(shēng)也可能不産生(shēng)一(yī)個結果集,這類沒有結果集的SQL語句,我(wǒ)(wǒ)們可以像下(xià)面這樣來執行:
pICommandText->Execute(NULL,IID_NULL,NULL,NULL,NULL);
這樣就不用關心SQL語句的結果集。當然對于返回結果集的查詢也可以這樣來執行,即直接丢棄結果集,當然這通常是沒有意義的。
下(xià)面依照慣例,在開(kāi)始正式的Rowset對象之旅前,讓我(wǒ)(wǒ)們首先來認識下(xià)Rowset對象究竟有哪些接口:
CoType TRowset
{
[mandatory] interface IAccessor;
[mandatory] interface IColumnsInfo;
[mandatory] interface IConvertType;
[mandatory] interface IRowset;
[mandatory] interface IRowsetInfo;
[optional] interface IChapteredRowset;
[optional] interface IColumnsInfo2;
[optional] interface IColumnsRowset;
[optional] interface IConnectionPointContainer;
[optional] interface IDBAsynchStatus;
[optional] interface IGetRow;
[optional] interface IRowsetChange;
[optional] interface IRowsetChapterMember;
[optional] interface IRowsetCurrentIndex;
[optional] interface IRowsetFind;
[optional] interface IRowsetIdentity;
[optional] interface IRowsetIndex;
[optional] interface IRowsetLocate;
[optional] interface IRowsetRefresh;
[optional] interface IRowsetScroll;
[optional] interface IRowsetUpdate;
[optional] interface IRowsetView;
[optional] interface ISupportErrorInfo;
[optional] interface IRowsetBookmark;
}
作爲補充,我(wǒ)(wǒ)們同時一(yī)看看上一(yī)會提到的多結果集對象的接口:
CoType TMultipleResults
{
[mandatory] interface IMultipleResults;
[optional] interface ISupportErrorInfo;
}
從Rowset對象的接口表中(zhōng),我(wǒ)(wǒ)們可以知(zhī)道這個對象都有什麽接口,以及那些接口是強制的,那些接口是可選的。在Rowset對象中(zhōng),我(wǒ)(wǒ)們經常用的接口就是IRowset、IColumnInfo和IAccessor,通過這幾個接口,才可以訪問到我(wǒ)(wǒ)們查詢得到的數據結果。同時要注意的就是,最終這個過程并不像前面的接口及方法那麽簡單,得到可訪問的數據就是OLEDB中(zhōng)最複雜(zá)的一(yī)個過程,如果這個過程徹底理解和掌握了,才能說是掌握了OLEDB編程的基礎,當然不要喪氣,在這裏我(wǒ)(wǒ)将詳細、細緻且有條理的介紹這個得到數據的過程:
一(yī)、得到結果集:
(請參看(三)文中(zhōng)關于得到結果集的方法示例,此處隻簡單放(fàng)一(yī)個示意性代碼)
pICommandText->Execute(NULL,IID_IRowset,NULL,NULL,(IUnknown**)&pIRowset)
二、得到列信息:
得到列信息是一(yī)個非常重要的過程,通過列信息我(wǒ)(wǒ)們可以知(zhī)道結果集的完整數據結構,這爲後續的創建訪問器,準備數據緩沖奠定了基礎。很多時候,在SQL的查詢語句中(zhōng)是沒有關于數據結構的信息的,這個信息是隐含在數據庫中(zhōng)的,而我(wǒ)(wǒ)們查詢得到結果集時就必須要知(zhī)道這個數據結構的信息,否則對數據的訪問将無從談起。要想得到一(yī)個結果集确切的數據結構信息,就要使用IColumnsInfo接口,下(xià)面的例子演示了如何得到一(yī)個結果集完整的列信息,已經如何安全的釋放(fàng)這個列信息:
IColumnsInfo * pIColumnsInfo = NULL;
ULONG cColumns = 0;
DBCOLUMNINFO * rgColumnInfo = NULL;
LPWSTR pStringBuffer = NULL;
HRESULT hr = pIRowset->QueryInterface(IID_IColumnsInfo,
(void**)&pIColumnsInfo));
hr = pIColumnsInfo->GetColumnInfo(&cColumns,
&rgColumnInfo,
&pStringBuffer
));
//如果成功了,那麽rgColumnInfo中(zhōng)已經包含了一(yī)個關于列信息數據結構的數組,
//數組元素的個數即cColumns 也就是最終的列數
//使用完畢後釋放(fàng)所有的資(zī)源及接口
CoTaskMemFree(rgColumnInfo);
CoTaskMemFree(pStringBuffer);
if( NULL != pIColumnsInfo )
{
pIColumnsInfo->Release();
}
上面的實例代碼顯示了如何從一(yī)個結果集查詢出IColumnsInfo接口,以及如何得到列信息的完整過程。在這個過程中(zhōng)特别需要注意的就是最後對rgColumnInfo數組和pStringBuffer内存的釋放(fàng),這裏使用了CoTaskMemFree這個COM庫函數,通常這樣就足夠了 ,而在OLEDB的幫助文檔中(zhōng)則說要使用IMalloc接口的Free方法來釋放(fàng),其實二者是等價的。一(yī)般的我(wǒ)(wǒ)們使用CoTaskMemFree函數即可,特别說明的是調用這個函數釋放(fàng)内存的時候不需要檢查被釋放(fàng)的指針是否爲空,它内部有檢查是否爲空的機制,所以直接調用皆可,這又(yòu)爲我(wǒ)(wǒ)們節約了2-3行代碼。
下(xià)面讓我(wǒ)(wǒ)們來認識下(xià)DBCOLUMNINFO這個結構,它的原型如下(xià):
typedef struct tagDBCOLUMNINFO
{
LPOLESTR pwszName;
ITypeInfo *pTypeInfo;
DBORDINAL iOrdinal;
DBCOLUMNFLAGS dwFlags;
DBLENGTH ulColumnSize;
DBTYPE wType;
BYTE bPrecision;
BYTE bScale;
DBID columnid;
}
DBCOLUMNINFO;
pwszName字段即爲字段的名稱,注意是個UNICODE字符串。如果查詢中(zhōng)沒有明确爲列指定名稱時,這個字段即爲空,也就是沒有列名。
pTypeInfo是一(yī)個保留待将來使用的接口,直到MSDAC2.6版爲止這個字段都沒有被使用,它永遠是NULL,我(wǒ)(wǒ)們忽略它即可。
iOrdinal就是字段在結果集中(zhōng)的序号,也就是表示這個列是結果集的第幾列,這個字段非常重要。特别注意的是,如果是結果集中(zhōng)的列,那麽序号是從1開(kāi)始的。而序号0是爲一(yī)些特殊用途保留的,比如我(wǒ)(wǒ)們打開(kāi)了結果集的書(shū)簽功能時,那麽序号0的列将是結果集的行号。關于書(shū)簽的内容将在後續的提高部分(fēn)中(zhōng)介紹。這裏大(dà)家隻要知(zhī)道1對應第一(yī)列,n對應第n列即可。
dwFlags字段描述了列的狀态,最重要的狀态就是描述該列是否爲一(yī)個BLOB型字段,或者該列是否可爲空等。它的類型爲DBCOLUMNFLAGS 是個枚舉類型,在MSDN中(zhōng)有關于這個枚舉類型的詳細描述,本文就不在贅述,隻在用到時特意說明。
ulColumnSize字段描述了列的大(dà)小(xiǎo),單位爲字節。需要注意得是,這個大(dà)小(xiǎo)僅對字符型列(即wType = DBTYPE_STR和wType = DBTYPE_WSTR)有意義,包括預定義的,或由查詢得到的字符串等,而對于其他類型的列該字段則設置爲一(yī)個~0的值,即所有bit位都爲一(yī)的值。
wType則說明了結果集列的數據類型,OLEDB中(zhōng)定義了完整的被支持的數據類型,此處将所有類型含義及其值列表出來,方便大(dà)家查詢:
enum DBTYPEENUM
{
// The following values exactly match VARENUM
// in Automation and may be used in VARIANT.
DBTYPE_EMPTY = 0,
DBTYPE_NULL = 1,
DBTYPE_I2 = 2,
DBTYPE_I4 = 3,
DBTYPE_R4 = 4,
DBTYPE_R8 = 5,
DBTYPE_CY = 6,
DBTYPE_DATE = 7,
DBTYPE_BSTR = 8,
DBTYPE_IDISPATCH = 9,
DBTYPE_ERROR = 10,
DBTYPE_BOOL = 11,
DBTYPE_VARIANT = 12,
DBTYPE_IUNKNOWN = 13,
DBTYPE_DECIMAL = 14,
DBTYPE_UI1 = 17,
DBTYPE_ARRAY = 0x2000,
DBTYPE_BYREF = 0x4000,
DBTYPE_I1 = 16,
DBTYPE_UI2 = 18,
DBTYPE_UI4 = 19,
// The following values exactly match VARENUM
// in Automation but cannot be used in VARIANT.
DBTYPE_I8 = 20,
DBTYPE_UI8 = 21,
DBTYPE_GUID = 72,
DBTYPE_VECTOR = 0x1000,
DBTYPE_FILETIME = 64,
DBTYPE_RESERVED = 0x8000,
// The following values are not in VARENUM in OLE.
DBTYPE_BYTES = 128,
DBTYPE_STR = 129,
DBTYPE_WSTR = 130,
DBTYPE_NUMERIC = 131,
DBTYPE_UDT = 132,
DBTYPE_DBDATE = 133,
DBTYPE_DBTIME = 134,
DBTYPE_DBTIMESTAMP = 135
DBTYPE_HCHAPTER = 136
DBTYPE_PROPVARIANT = 138,
DBTYPE_VARNUMERIC = 139
};
需要特别注意的是,這裏的數據類型不是數據庫支持的數據類型,或者說并不是所有的數據庫都支持這些所有的數據類型,這僅是一(yī)個所有可能數據類型的全部概括,當然也有一(yī)些數據庫中(zhōng)的類型并不在這個列表中(zhōng),這時往往數據庫對應的OLEDB接口提供程序都做了很好的轉換,已經轉換成了這個列表中(zhōng)所具有的類型,因此不用擔心會碰到不支持的數據類型。
bPrecision和bScale字段就不用介紹了,一(yī)個是精度,一(yī)個是小(xiǎo)數位數,他們對于數值型字段是非常重要的。
columnid字段是該列在數據庫系統字典表中(zhōng)的id号,這個id号也很重要,當我(wǒ)(wǒ)們需要一(yī)些關于列的其他更多信息時往往就需要這個id号。
至此關于列的信息以及列信息的結構體(tǐ)就介紹完了,也許你會很奇怪,爲什麽這個東西我(wǒ)(wǒ)會啰嗦這麽多,這是因爲這對我(wǒ)(wǒ)們後續的操作是非常非常重要的。看了接下(xià)來的操作你就知(zhī)道爲什麽這個會這麽重要了。
三、創建一(yī)個綁定:
綁定是OLEDB中(zhōng)最重要最核心的概念之一(yī),它是我(wǒ)(wǒ)們訪問數據的關鍵操作,也是比較難理解的一(yī)個操作。其實對綁定最簡單的理解就是:安排得到數據的内存擺放(fàng)方式,并将這一(yī)方式告訴數據提供者,讓它按要求将數據擺放(fàng)到我(wǒ)(wǒ)們指定的内存中(zhōng)。爲了這個目的,我(wǒ)(wǒ)們就需要自己創建一(yī)個被稱作DBBINDING的數組,首先我(wǒ)(wǒ)們來看下(xià)這個結構體(tǐ)的樣子:
typedef struct tagDBBINDING
{
DBORDINAL iOrdinal;
DBBYTEOFFSET obValue;
DBBYTEOFFSET obLength;
DBBYTEOFFSET obStatus;
ITypeInfo *pTypeInfo;
DBOBJECT *pObject;
DBBINDEXT *pBindExt;
DBPART dwPart;
DBMEMOWNER dwMemOwner;
DBPARAMIO eParamIO;
DBLENGTH cbMaxLen;
DWORD dwFlags;
DBTYPE wType;
BYTE bPrecision;
BYTE bScale;
} DBBINDING;
乍一(yī)看這個結構和前面的DBCOLUMNINFO結構很相似,其實他們的字段大(dà)多數确實是一(yī)緻的,甚至可以直接使用DBCOLUMNINFO對DBBINDING進行賦值,但是二者字段的含義确是完全不同的,隻有理解這二者的差别,才能真正玩轉OLEDB。首先DBCOLUMNINFO是數據提供者給你的信息,它是固定的,對相同的查詢來說,列總是相同的,因此數據提供者返回的DBCOLUMNINFO數組也是固定的。其次DBBINDING是你作爲數據消費(fèi)者創建之後給數據提供者的一(yī)個結構數組,它的内容則由你來完全控制,通過這個結構我(wǒ)(wǒ)們可以指定數據提供者最終将數據擺放(fàng)成我(wǒ)(wǒ)們指定的格式,或者進行指定的數據類型轉換。
下(xià)面的例子展示了如何完成這個過程:
ULONG cColumns;
DBCOLUMNINFO * rgColumnInfo = NULL;
LPWSTR pStringBuffer = NULL;
IColumnsInfo * pIColumnsInfo = NULL;
ULONG iCol;
ULONG dwOffset = 0;
DBBINDING * rgBindings = NULL;
pIRowset->QueryInterface(IID_IColumnsInfo,(void**)&pIColumnsInfo));
pIColumnsInfo->GetColumnInfo(&cColumns, &rgColumnInfo,
&pStringBuffer));
rgBindings = (DBBINDING*)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
cColumns * sizeof(DBBINDING));
for( iCol = 0; iCol < cColumns; iCol++ )
{
rgBindings[iCol].iOrdinal = rgColumnInfo[iCol].iOrdinal;
rgBindings[iCol].dwPart = DBPART_VALUE|DBPART_LENGTH|DBPART_STATUS;
rgBindings[iCol].obStatus = dwOffset;
rgBindings[iCol].obLength = dwOffset + sizeof(DBSTATUS);
rgBindings[iCol].obValue = dwOffset+sizeof(DBSTATUS)+sizeof(ULONG);
rgBindings[iCol].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
rgBindings[iCol].eParamIO = DBPARAMIO_NOTPARAM;
rgBindings[iCol].bPrecision = rgColumnInfo[iCol].bPrecision;
rgBindings[iCol].bScale = rgColumnInfo[iCol].bScale;
rgBindings[iCol].wType = rgColumnInfo[iCol].wType;
rgBindings[iCol].cbMaxLen = rgColumnInfo[iCol].ulColumnSize;
dwOffset = rgBindings[iCol].cbMaxLen + rgBindings[iCol].obValue;
dwOffset = ROUNDUP(dwOffset);
}
CoTaskMemFree(rgColumnInfo);
CoTaskMemFree(pStringBuffer);
if( pIColumnsInfo )
{
pIColumnsInfo->Release();
}
在上面的例子代碼中(zhōng),主體(tǐ)循環中(zhōng)演示了如何遍曆得到的列信息數組,以及如何使用這個列信息創建一(yī)個DBBINDING數組。
在代碼中(zhōng),要特别留意的就是下(xià)面這四行:
rgBindings[iCol].dwPart = DBPART_VALUE|DBPART_LENGTH|DBPART_STATUS;
rgBindings[iCol].obStatus = dwOffset;
rgBindings[iCol].obLength = dwOffset + sizeof(DBSTATUS);
rgBindings[iCol].obValue = dwOffset+sizeof(DBSTATUS)+sizeof(ULONG);
其中(zhōng)第一(yī)句指明數據提供者最終提交數據時必須包含的信息,這裏指定了列值(DBPART_VALUE),長度(DBPART_LENGTH)和狀态(DBPART_STATUS)共3個信息,列值不用說就是要放(fàng)字段的最終結果值,長度就是這個字段占用的字節長度,列狀态中(zhōng)将存放(fàng)的是值的狀态比如是否爲空等。
後面3句則明确的指出了以上三個信息在内存中(zhōng)擺放(fàng)的偏移位置,通常按習慣,我(wǒ)(wǒ)們要求數據提供者先擺放(fàng)狀态,再擺放(fàng)長度,最後擺放(fàng)數據。
循環結束後dwOffset的值就是一(yī)行記錄數據所需要的内存的大(dà)小(xiǎo)。
至此一(yī)個簡單的DBBINDING就創建好了。當然實際中(zhōng)創建這個結構數組時可不是這麽簡單就行了,還需要考慮很多問題,比如BLOB型的字段如何處理等等。作爲基礎的了解上面的例子已經足夠了,關于DBBINDING更高級的内容我(wǒ)(wǒ)将放(fàng)在後續的高級内容中(zhōng)。這裏先快速給大(dà)家打個基礎。
四、創建訪問器:
再有了綁定結構之後,接下(xià)來要做的工(gōng)作就是通知(zhī)數據提供者按我(wǒ)(wǒ)們的要求将數據進行一(yī)個“格式化”,這個過程就是創建一(yī)個訪問器。創建訪問器就要使用IAccessor接口,同樣這個接口也是從IRowset查詢的來,代表訪問器的标志(zhì)則是一(yī)個類型爲HACCESSOR的句柄。
下(xià)面的例子代碼演示了如何創建一(yī)個訪問器:
HACCESSOR * phAccessor,
IAccessor * pIAccessor = NULL;
pIRowset->QueryInterface(IID_IAccessor,(void**)&pIAccessor));
pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA,
cColumns,rgBindings,0,phAccessor,NULL));
if( pIAccessor )
{
pIAccessor->Release();
}
上面的例子代碼中(zhōng)直接使用了前一(yī)個例子中(zhōng)創建的綁定結構,這段例子代碼中(zhōng)唯一(yī)需要注意的就是最後我(wǒ)(wǒ)們直接釋放(fàng)了IAccessor接口,這也表示創建了訪問器後這個接口就沒有用了,如果需要我(wǒ)(wǒ)們其實也可以随時從同一(yī)IRowset接口再查詢出這個接口即可,因爲這兩個接口表示的結果集對象是同一(yī)個。
至此數據提供者也知(zhī)道了我(wǒ)(wǒ)們要以什麽具體(tǐ)的内存格式來得到數據,接下(xià)去(qù)就是真正的得到數據了。
五、得到數據:
有了前面4步的基礎工(gōng)作,最後我(wǒ)(wǒ)們終于可以正式的得到我(wǒ)(wǒ)們的數據了,爲了描述的連貫性這裏先将例子代碼寫出來:
void * pData = NULL;
ULONG cRowsObtained;
HROW * rghRows = NULL;
ULONG iRow;
LONG cRows = 10;//一(yī)次讀取10行
void * pCurData;
//分(fēn)配cRows行數據的緩沖,然後反複讀取cRows行到這裏,然後逐行處理之
pData = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY, dwOffset * cRows);
while( S_OK == pIRowset->GetNextRows(DB_NULL_HCHAPTER,
0,cRows,&cRowsObtained, &rghRows)) )
{//循環讀取數據,每次循環默認讀取cRows行,實際讀取到cRowsObtained行
for( iRow = 0; iRow < cRowsObtained; iRow++ )
{
pCurData = (BYTE*)pData + (dwOffset * iRow);
pIRowset->GetData( rghRows[iRow],hAccessor,pCurData));
//pCurData中(zhōng)已經包含了結果數據,顯示或者進行處理
......
}
if( cRowsObtained )
{//釋放(fàng)行句柄數組
pIRowset->ReleaseRows(cRowsObtained,rghRows,NULL,
NULL,NULL));
}
CoTaskMemFree(rghRows);
rghRows = NULL;
}
HeapFree(GetProcessHeap(),0,pData);
if( pIRowset )
{
pIRowset->Release();
}
上面的例子代碼中(zhōng)也直接使用了第三步和第四步中(zhōng)的一(yī)些變量,大(dà)家要注意每個變量的含義,dwOffset實際在這裏已經表示一(yī)行數據需要的内存大(dà)小(xiǎo)。
在這裏又(yòu)多了一(yī)個類型HROW,這個類型表示行的句柄,這裏使用的是HROW的數組,讀出多少行,數組中(zhōng)就有多少行的句柄,然後在使用句柄調用GetData将數據讀到指定的内存位置。最後每次都釋放(fàng)了這個數組,因爲數據已經讀進了我(wǒ)(wǒ)們分(fēn)配的緩存中(zhōng),這個句柄數組也就變得沒有意義了。最終數據讀取完畢,我(wǒ)(wǒ)們也就釋放(fàng)了IRowset接口。
至此進過5個複雜(zá)而難于理解的步驟,我(wǒ)(wǒ)們終于讀到了我(wǒ)(wǒ)們想要的數據,每一(yī)行數據都在那個pCurData中(zhōng),并且嚴格按照我(wǒ)(wǒ)們指定的DBBINDING結構中(zhōng)的幾個Offset偏移地址擺放(fàng),處理行數據内存時,也就按照這幾個偏移就的得了具體(tǐ)的每一(yī)行每一(yī)個列的值。
大(dà)體(tǐ)上如何最終得到行數據的基礎性内容也就介紹完了,整個的OLEDB基礎性的知(zhī)識也就介紹完了,之後的後續文章中(zhōng)我(wǒ)(wǒ)将繼續介紹一(yī)些更加細節性的問題,或者是更加深入和專題化的問題,也就是提高部分(fēn)的内容,在繼續後續内容之前,也希望大(dà)家對之前的所有的内容都有所了解和掌握。
不管怎麽說OLEDB接口雖然功能強大(dà),但其複雜(zá)性也是讓人望而卻步的。通過前面4篇文章的介紹,也隻是很基礎的内容,同時限于本人的知(zhī)識能力水平,錯漏之處也在所難免,在此也懇請大(dà)家對這一(yī)系列文章提出寶貴的意見或問題,以便我(wǒ)(wǒ)之後改進提高。最終的目的無非就是讓大(dà)家都真正的掌握OLEDB這個“神兵利器”,做到“攻無不克,戰無不勝!”。
pICommandText->Execute(NULL,IID_NULL,NULL,NULL,NULL);
這樣就不用關心SQL語句的結果集。當然對于返回結果集的查詢也可以這樣來執行,即直接丢棄結果集,當然這通常是沒有意義的。
下(xià)面依照慣例,在開(kāi)始正式的Rowset對象之旅前,讓我(wǒ)(wǒ)們首先來認識下(xià)Rowset對象究竟有哪些接口:
CoType TRowset
{
[mandatory] interface IAccessor;
[mandatory] interface IColumnsInfo;
[mandatory] interface IConvertType;
[mandatory] interface IRowset;
[mandatory] interface IRowsetInfo;
[optional] interface IChapteredRowset;
[optional] interface IColumnsInfo2;
[optional] interface IColumnsRowset;
[optional] interface IConnectionPointContainer;
[optional] interface IDBAsynchStatus;
[optional] interface IGetRow;
[optional] interface IRowsetChange;
[optional] interface IRowsetChapterMember;
[optional] interface IRowsetCurrentIndex;
[optional] interface IRowsetFind;
[optional] interface IRowsetIdentity;
[optional] interface IRowsetIndex;
[optional] interface IRowsetLocate;
[optional] interface IRowsetRefresh;
[optional] interface IRowsetScroll;
[optional] interface IRowsetUpdate;
[optional] interface IRowsetView;
[optional] interface ISupportErrorInfo;
[optional] interface IRowsetBookmark;
}
作爲補充,我(wǒ)(wǒ)們同時一(yī)看看上一(yī)會提到的多結果集對象的接口:
CoType TMultipleResults
{
[mandatory] interface IMultipleResults;
[optional] interface ISupportErrorInfo;
}
從Rowset對象的接口表中(zhōng),我(wǒ)(wǒ)們可以知(zhī)道這個對象都有什麽接口,以及那些接口是強制的,那些接口是可選的。在Rowset對象中(zhōng),我(wǒ)(wǒ)們經常用的接口就是IRowset、IColumnInfo和IAccessor,通過這幾個接口,才可以訪問到我(wǒ)(wǒ)們查詢得到的數據結果。同時要注意的就是,最終這個過程并不像前面的接口及方法那麽簡單,得到可訪問的數據就是OLEDB中(zhōng)最複雜(zá)的一(yī)個過程,如果這個過程徹底理解和掌握了,才能說是掌握了OLEDB編程的基礎,當然不要喪氣,在這裏我(wǒ)(wǒ)将詳細、細緻且有條理的介紹這個得到數據的過程:
一(yī)、得到結果集:
(請參看(三)文中(zhōng)關于得到結果集的方法示例,此處隻簡單放(fàng)一(yī)個示意性代碼)
pICommandText->Execute(NULL,IID_IRowset,NULL,NULL,(IUnknown**)&pIRowset)
二、得到列信息:
得到列信息是一(yī)個非常重要的過程,通過列信息我(wǒ)(wǒ)們可以知(zhī)道結果集的完整數據結構,這爲後續的創建訪問器,準備數據緩沖奠定了基礎。很多時候,在SQL的查詢語句中(zhōng)是沒有關于數據結構的信息的,這個信息是隐含在數據庫中(zhōng)的,而我(wǒ)(wǒ)們查詢得到結果集時就必須要知(zhī)道這個數據結構的信息,否則對數據的訪問将無從談起。要想得到一(yī)個結果集确切的數據結構信息,就要使用IColumnsInfo接口,下(xià)面的例子演示了如何得到一(yī)個結果集完整的列信息,已經如何安全的釋放(fàng)這個列信息:
IColumnsInfo * pIColumnsInfo = NULL;
ULONG cColumns = 0;
DBCOLUMNINFO * rgColumnInfo = NULL;
LPWSTR pStringBuffer = NULL;
HRESULT hr = pIRowset->QueryInterface(IID_IColumnsInfo,
(void**)&pIColumnsInfo));
hr = pIColumnsInfo->GetColumnInfo(&cColumns,
&rgColumnInfo,
&pStringBuffer
));
//如果成功了,那麽rgColumnInfo中(zhōng)已經包含了一(yī)個關于列信息數據結構的數組,
//數組元素的個數即cColumns 也就是最終的列數
//使用完畢後釋放(fàng)所有的資(zī)源及接口
CoTaskMemFree(rgColumnInfo);
CoTaskMemFree(pStringBuffer);
if( NULL != pIColumnsInfo )
{
pIColumnsInfo->Release();
}
上面的實例代碼顯示了如何從一(yī)個結果集查詢出IColumnsInfo接口,以及如何得到列信息的完整過程。在這個過程中(zhōng)特别需要注意的就是最後對rgColumnInfo數組和pStringBuffer内存的釋放(fàng),這裏使用了CoTaskMemFree這個COM庫函數,通常這樣就足夠了 ,而在OLEDB的幫助文檔中(zhōng)則說要使用IMalloc接口的Free方法來釋放(fàng),其實二者是等價的。一(yī)般的我(wǒ)(wǒ)們使用CoTaskMemFree函數即可,特别說明的是調用這個函數釋放(fàng)内存的時候不需要檢查被釋放(fàng)的指針是否爲空,它内部有檢查是否爲空的機制,所以直接調用皆可,這又(yòu)爲我(wǒ)(wǒ)們節約了2-3行代碼。
下(xià)面讓我(wǒ)(wǒ)們來認識下(xià)DBCOLUMNINFO這個結構,它的原型如下(xià):
typedef struct tagDBCOLUMNINFO
{
LPOLESTR pwszName;
ITypeInfo *pTypeInfo;
DBORDINAL iOrdinal;
DBCOLUMNFLAGS dwFlags;
DBLENGTH ulColumnSize;
DBTYPE wType;
BYTE bPrecision;
BYTE bScale;
DBID columnid;
}
DBCOLUMNINFO;
pwszName字段即爲字段的名稱,注意是個UNICODE字符串。如果查詢中(zhōng)沒有明确爲列指定名稱時,這個字段即爲空,也就是沒有列名。
pTypeInfo是一(yī)個保留待将來使用的接口,直到MSDAC2.6版爲止這個字段都沒有被使用,它永遠是NULL,我(wǒ)(wǒ)們忽略它即可。
iOrdinal就是字段在結果集中(zhōng)的序号,也就是表示這個列是結果集的第幾列,這個字段非常重要。特别注意的是,如果是結果集中(zhōng)的列,那麽序号是從1開(kāi)始的。而序号0是爲一(yī)些特殊用途保留的,比如我(wǒ)(wǒ)們打開(kāi)了結果集的書(shū)簽功能時,那麽序号0的列将是結果集的行号。關于書(shū)簽的内容将在後續的提高部分(fēn)中(zhōng)介紹。這裏大(dà)家隻要知(zhī)道1對應第一(yī)列,n對應第n列即可。
dwFlags字段描述了列的狀态,最重要的狀态就是描述該列是否爲一(yī)個BLOB型字段,或者該列是否可爲空等。它的類型爲DBCOLUMNFLAGS 是個枚舉類型,在MSDN中(zhōng)有關于這個枚舉類型的詳細描述,本文就不在贅述,隻在用到時特意說明。
ulColumnSize字段描述了列的大(dà)小(xiǎo),單位爲字節。需要注意得是,這個大(dà)小(xiǎo)僅對字符型列(即wType = DBTYPE_STR和wType = DBTYPE_WSTR)有意義,包括預定義的,或由查詢得到的字符串等,而對于其他類型的列該字段則設置爲一(yī)個~0的值,即所有bit位都爲一(yī)的值。
wType則說明了結果集列的數據類型,OLEDB中(zhōng)定義了完整的被支持的數據類型,此處将所有類型含義及其值列表出來,方便大(dà)家查詢:
enum DBTYPEENUM
{
// The following values exactly match VARENUM
// in Automation and may be used in VARIANT.
DBTYPE_EMPTY = 0,
DBTYPE_NULL = 1,
DBTYPE_I2 = 2,
DBTYPE_I4 = 3,
DBTYPE_R4 = 4,
DBTYPE_R8 = 5,
DBTYPE_CY = 6,
DBTYPE_DATE = 7,
DBTYPE_BSTR = 8,
DBTYPE_IDISPATCH = 9,
DBTYPE_ERROR = 10,
DBTYPE_BOOL = 11,
DBTYPE_VARIANT = 12,
DBTYPE_IUNKNOWN = 13,
DBTYPE_DECIMAL = 14,
DBTYPE_UI1 = 17,
DBTYPE_ARRAY = 0x2000,
DBTYPE_BYREF = 0x4000,
DBTYPE_I1 = 16,
DBTYPE_UI2 = 18,
DBTYPE_UI4 = 19,
// The following values exactly match VARENUM
// in Automation but cannot be used in VARIANT.
DBTYPE_I8 = 20,
DBTYPE_UI8 = 21,
DBTYPE_GUID = 72,
DBTYPE_VECTOR = 0x1000,
DBTYPE_FILETIME = 64,
DBTYPE_RESERVED = 0x8000,
// The following values are not in VARENUM in OLE.
DBTYPE_BYTES = 128,
DBTYPE_STR = 129,
DBTYPE_WSTR = 130,
DBTYPE_NUMERIC = 131,
DBTYPE_UDT = 132,
DBTYPE_DBDATE = 133,
DBTYPE_DBTIME = 134,
DBTYPE_DBTIMESTAMP = 135
DBTYPE_HCHAPTER = 136
DBTYPE_PROPVARIANT = 138,
DBTYPE_VARNUMERIC = 139
};
需要特别注意的是,這裏的數據類型不是數據庫支持的數據類型,或者說并不是所有的數據庫都支持這些所有的數據類型,這僅是一(yī)個所有可能數據類型的全部概括,當然也有一(yī)些數據庫中(zhōng)的類型并不在這個列表中(zhōng),這時往往數據庫對應的OLEDB接口提供程序都做了很好的轉換,已經轉換成了這個列表中(zhōng)所具有的類型,因此不用擔心會碰到不支持的數據類型。
bPrecision和bScale字段就不用介紹了,一(yī)個是精度,一(yī)個是小(xiǎo)數位數,他們對于數值型字段是非常重要的。
columnid字段是該列在數據庫系統字典表中(zhōng)的id号,這個id号也很重要,當我(wǒ)(wǒ)們需要一(yī)些關于列的其他更多信息時往往就需要這個id号。
至此關于列的信息以及列信息的結構體(tǐ)就介紹完了,也許你會很奇怪,爲什麽這個東西我(wǒ)(wǒ)會啰嗦這麽多,這是因爲這對我(wǒ)(wǒ)們後續的操作是非常非常重要的。看了接下(xià)來的操作你就知(zhī)道爲什麽這個會這麽重要了。
三、創建一(yī)個綁定:
綁定是OLEDB中(zhōng)最重要最核心的概念之一(yī),它是我(wǒ)(wǒ)們訪問數據的關鍵操作,也是比較難理解的一(yī)個操作。其實對綁定最簡單的理解就是:安排得到數據的内存擺放(fàng)方式,并将這一(yī)方式告訴數據提供者,讓它按要求将數據擺放(fàng)到我(wǒ)(wǒ)們指定的内存中(zhōng)。爲了這個目的,我(wǒ)(wǒ)們就需要自己創建一(yī)個被稱作DBBINDING的數組,首先我(wǒ)(wǒ)們來看下(xià)這個結構體(tǐ)的樣子:
typedef struct tagDBBINDING
{
DBORDINAL iOrdinal;
DBBYTEOFFSET obValue;
DBBYTEOFFSET obLength;
DBBYTEOFFSET obStatus;
ITypeInfo *pTypeInfo;
DBOBJECT *pObject;
DBBINDEXT *pBindExt;
DBPART dwPart;
DBMEMOWNER dwMemOwner;
DBPARAMIO eParamIO;
DBLENGTH cbMaxLen;
DWORD dwFlags;
DBTYPE wType;
BYTE bPrecision;
BYTE bScale;
} DBBINDING;
乍一(yī)看這個結構和前面的DBCOLUMNINFO結構很相似,其實他們的字段大(dà)多數确實是一(yī)緻的,甚至可以直接使用DBCOLUMNINFO對DBBINDING進行賦值,但是二者字段的含義确是完全不同的,隻有理解這二者的差别,才能真正玩轉OLEDB。首先DBCOLUMNINFO是數據提供者給你的信息,它是固定的,對相同的查詢來說,列總是相同的,因此數據提供者返回的DBCOLUMNINFO數組也是固定的。其次DBBINDING是你作爲數據消費(fèi)者創建之後給數據提供者的一(yī)個結構數組,它的内容則由你來完全控制,通過這個結構我(wǒ)(wǒ)們可以指定數據提供者最終将數據擺放(fàng)成我(wǒ)(wǒ)們指定的格式,或者進行指定的數據類型轉換。
下(xià)面的例子展示了如何完成這個過程:
ULONG cColumns;
DBCOLUMNINFO * rgColumnInfo = NULL;
LPWSTR pStringBuffer = NULL;
IColumnsInfo * pIColumnsInfo = NULL;
ULONG iCol;
ULONG dwOffset = 0;
DBBINDING * rgBindings = NULL;
pIRowset->QueryInterface(IID_IColumnsInfo,(void**)&pIColumnsInfo));
pIColumnsInfo->GetColumnInfo(&cColumns, &rgColumnInfo,
&pStringBuffer));
rgBindings = (DBBINDING*)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
cColumns * sizeof(DBBINDING));
for( iCol = 0; iCol < cColumns; iCol++ )
{
rgBindings[iCol].iOrdinal = rgColumnInfo[iCol].iOrdinal;
rgBindings[iCol].dwPart = DBPART_VALUE|DBPART_LENGTH|DBPART_STATUS;
rgBindings[iCol].obStatus = dwOffset;
rgBindings[iCol].obLength = dwOffset + sizeof(DBSTATUS);
rgBindings[iCol].obValue = dwOffset+sizeof(DBSTATUS)+sizeof(ULONG);
rgBindings[iCol].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
rgBindings[iCol].eParamIO = DBPARAMIO_NOTPARAM;
rgBindings[iCol].bPrecision = rgColumnInfo[iCol].bPrecision;
rgBindings[iCol].bScale = rgColumnInfo[iCol].bScale;
rgBindings[iCol].wType = rgColumnInfo[iCol].wType;
rgBindings[iCol].cbMaxLen = rgColumnInfo[iCol].ulColumnSize;
dwOffset = rgBindings[iCol].cbMaxLen + rgBindings[iCol].obValue;
dwOffset = ROUNDUP(dwOffset);
}
CoTaskMemFree(rgColumnInfo);
CoTaskMemFree(pStringBuffer);
if( pIColumnsInfo )
{
pIColumnsInfo->Release();
}
在上面的例子代碼中(zhōng),主體(tǐ)循環中(zhōng)演示了如何遍曆得到的列信息數組,以及如何使用這個列信息創建一(yī)個DBBINDING數組。
在代碼中(zhōng),要特别留意的就是下(xià)面這四行:
rgBindings[iCol].dwPart = DBPART_VALUE|DBPART_LENGTH|DBPART_STATUS;
rgBindings[iCol].obStatus = dwOffset;
rgBindings[iCol].obLength = dwOffset + sizeof(DBSTATUS);
rgBindings[iCol].obValue = dwOffset+sizeof(DBSTATUS)+sizeof(ULONG);
其中(zhōng)第一(yī)句指明數據提供者最終提交數據時必須包含的信息,這裏指定了列值(DBPART_VALUE),長度(DBPART_LENGTH)和狀态(DBPART_STATUS)共3個信息,列值不用說就是要放(fàng)字段的最終結果值,長度就是這個字段占用的字節長度,列狀态中(zhōng)将存放(fàng)的是值的狀态比如是否爲空等。
後面3句則明确的指出了以上三個信息在内存中(zhōng)擺放(fàng)的偏移位置,通常按習慣,我(wǒ)(wǒ)們要求數據提供者先擺放(fàng)狀态,再擺放(fàng)長度,最後擺放(fàng)數據。
循環結束後dwOffset的值就是一(yī)行記錄數據所需要的内存的大(dà)小(xiǎo)。
至此一(yī)個簡單的DBBINDING就創建好了。當然實際中(zhōng)創建這個結構數組時可不是這麽簡單就行了,還需要考慮很多問題,比如BLOB型的字段如何處理等等。作爲基礎的了解上面的例子已經足夠了,關于DBBINDING更高級的内容我(wǒ)(wǒ)将放(fàng)在後續的高級内容中(zhōng)。這裏先快速給大(dà)家打個基礎。
四、創建訪問器:
再有了綁定結構之後,接下(xià)來要做的工(gōng)作就是通知(zhī)數據提供者按我(wǒ)(wǒ)們的要求将數據進行一(yī)個“格式化”,這個過程就是創建一(yī)個訪問器。創建訪問器就要使用IAccessor接口,同樣這個接口也是從IRowset查詢的來,代表訪問器的标志(zhì)則是一(yī)個類型爲HACCESSOR的句柄。
下(xià)面的例子代碼演示了如何創建一(yī)個訪問器:
HACCESSOR * phAccessor,
IAccessor * pIAccessor = NULL;
pIRowset->QueryInterface(IID_IAccessor,(void**)&pIAccessor));
pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA,
cColumns,rgBindings,0,phAccessor,NULL));
if( pIAccessor )
{
pIAccessor->Release();
}
上面的例子代碼中(zhōng)直接使用了前一(yī)個例子中(zhōng)創建的綁定結構,這段例子代碼中(zhōng)唯一(yī)需要注意的就是最後我(wǒ)(wǒ)們直接釋放(fàng)了IAccessor接口,這也表示創建了訪問器後這個接口就沒有用了,如果需要我(wǒ)(wǒ)們其實也可以随時從同一(yī)IRowset接口再查詢出這個接口即可,因爲這兩個接口表示的結果集對象是同一(yī)個。
至此數據提供者也知(zhī)道了我(wǒ)(wǒ)們要以什麽具體(tǐ)的内存格式來得到數據,接下(xià)去(qù)就是真正的得到數據了。
五、得到數據:
有了前面4步的基礎工(gōng)作,最後我(wǒ)(wǒ)們終于可以正式的得到我(wǒ)(wǒ)們的數據了,爲了描述的連貫性這裏先将例子代碼寫出來:
void * pData = NULL;
ULONG cRowsObtained;
HROW * rghRows = NULL;
ULONG iRow;
LONG cRows = 10;//一(yī)次讀取10行
void * pCurData;
//分(fēn)配cRows行數據的緩沖,然後反複讀取cRows行到這裏,然後逐行處理之
pData = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY, dwOffset * cRows);
while( S_OK == pIRowset->GetNextRows(DB_NULL_HCHAPTER,
0,cRows,&cRowsObtained, &rghRows)) )
{//循環讀取數據,每次循環默認讀取cRows行,實際讀取到cRowsObtained行
for( iRow = 0; iRow < cRowsObtained; iRow++ )
{
pCurData = (BYTE*)pData + (dwOffset * iRow);
pIRowset->GetData( rghRows[iRow],hAccessor,pCurData));
//pCurData中(zhōng)已經包含了結果數據,顯示或者進行處理
......
}
if( cRowsObtained )
{//釋放(fàng)行句柄數組
pIRowset->ReleaseRows(cRowsObtained,rghRows,NULL,
NULL,NULL));
}
CoTaskMemFree(rghRows);
rghRows = NULL;
}
HeapFree(GetProcessHeap(),0,pData);
if( pIRowset )
{
pIRowset->Release();
}
上面的例子代碼中(zhōng)也直接使用了第三步和第四步中(zhōng)的一(yī)些變量,大(dà)家要注意每個變量的含義,dwOffset實際在這裏已經表示一(yī)行數據需要的内存大(dà)小(xiǎo)。
在這裏又(yòu)多了一(yī)個類型HROW,這個類型表示行的句柄,這裏使用的是HROW的數組,讀出多少行,數組中(zhōng)就有多少行的句柄,然後在使用句柄調用GetData将數據讀到指定的内存位置。最後每次都釋放(fàng)了這個數組,因爲數據已經讀進了我(wǒ)(wǒ)們分(fēn)配的緩存中(zhōng),這個句柄數組也就變得沒有意義了。最終數據讀取完畢,我(wǒ)(wǒ)們也就釋放(fàng)了IRowset接口。
至此進過5個複雜(zá)而難于理解的步驟,我(wǒ)(wǒ)們終于讀到了我(wǒ)(wǒ)們想要的數據,每一(yī)行數據都在那個pCurData中(zhōng),并且嚴格按照我(wǒ)(wǒ)們指定的DBBINDING結構中(zhōng)的幾個Offset偏移地址擺放(fàng),處理行數據内存時,也就按照這幾個偏移就的得了具體(tǐ)的每一(yī)行每一(yī)個列的值。
大(dà)體(tǐ)上如何最終得到行數據的基礎性内容也就介紹完了,整個的OLEDB基礎性的知(zhī)識也就介紹完了,之後的後續文章中(zhōng)我(wǒ)(wǒ)将繼續介紹一(yī)些更加細節性的問題,或者是更加深入和專題化的問題,也就是提高部分(fēn)的内容,在繼續後續内容之前,也希望大(dà)家對之前的所有的内容都有所了解和掌握。
不管怎麽說OLEDB接口雖然功能強大(dà),但其複雜(zá)性也是讓人望而卻步的。通過前面4篇文章的介紹,也隻是很基礎的内容,同時限于本人的知(zhī)識能力水平,錯漏之處也在所難免,在此也懇請大(dà)家對這一(yī)系列文章提出寶貴的意見或問題,以便我(wǒ)(wǒ)之後改進提高。最終的目的無非就是讓大(dà)家都真正的掌握OLEDB這個“神兵利器”,做到“攻無不克,戰無不勝!”。