使用OLEDB编程
本文整理自博客:http://gamebabyrocksun.blog.163.com/blog/static/571534632008101083957499/
一,打开数据库连接
1,要完成一个数据库访问任务,第一步则是打开连接,在OLEDB2.0以前的版本中,可以如下操作:
#define COM_NO_WINDOWS_H //如果已经包含了Windows.h或不使用其他Windows 库函数时 #define DBINITCONSTANTS #define INITGUID #define OLEDBVER 0x0250 #include <oledb.h> #include <oledberr.h> IDBInitialize *pIDBInitialize = NULL; CoCreateInstance(CLSID_MSDASQL, NULL,CLSCTX_INPROC_SERVER, IID_IDBInitialize, (void**)&pIDBInitialize);
如果你使用的是SQL Server 2005以上的数据库,则有以下可选方式:
#include <sqlncli.h>
CoCreateInstance( CLSID_SQLNCLI10,NULL,CLSCTX_INPROC_SERVER, IID_IDBInitialize,(void **) &pIDBInitialize);
这是OLEDB2.0以前的做法。
在OLEDB2.0以后,推出了以下两个接口:IDataInitialize和IDBPromptInitialize,其中,后者将弹出以下对话框:
其创建代码如下:
IDBPromptInitialize* pIDBPromptInitialize = NULL; CoCreateInstance(CLSID_DataLinks, NULL, CLSCTX_INPROC_SERVER, IID_IDBPromptInitialize, (void **)&pIDBPromptInitialize); IDBInitialize *pIDBInitialize = NULL; //下面这句将弹出前面所说的对话框 pIDBPromptInitialize->PromptDataSource(NULL, hWndParent, DBPROMPTOPTIONS_PROPERTYSHEET, 0, NULL, NULL, IID_IDBInitialize (IUnknown **)&pIDBInitialize); pIDBInitialize->Initialize();//根据对话框采集的参数连接到指定的数据库
IDataInitialize的使用方法如下:
IDataInitialize* pIDataInitialize = NULL;
CoCreateInstance(CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER, IID_IDataInitialize,(void**)&pIDataInitialize);
IDBInitialize *pIDBInitialize = NULL;
pIDataInitialize->CreateDBInstance(CLSID_MSDASQL, NULL, CLSCTX_INPROC_SERVER, NULL, IID_IDBInitialize (IUnknown**)&pIDBInitialize);
2,在创建了IDBInitialize接口之后(使用IDBPromptInitialize这种方式创建的除外),我们需要详细指定连接数据的各种参数,在OLEDB中这些参数称为属性,属性以组划分,每组属性称为属性集合。
1 DBPROP InitProperties[4]; 2 DBPROPSET rgInitPropSet[1]; 3 4 //初始化属性值变量 5 for ( i = 0 ; i < 4 ; i++ ) 6 { 7 VariantInit(&InitProperties[i].vValue); 8 } 9 10 //指定数据库实例名,这里使用了别名local,指定本地默认实例 11 InitProperties[0].dwPropertyID = DBPROP_INIT_DATASOURCE; 12 InitProperties[0].vValue.vt = VT_BSTR; 13 InitProperties[0].vValue.bstrVal= SysAllocString(L"(local)"); 14 InitProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED; 15 InitProperties[0].colid = DB_NULLID; 16 17 //指定数据库名 18 InitProperties[1].dwPropertyID = DBPROP_INIT_CATALOG; 19 InitProperties[1].vValue.vt = VT_BSTR; 20 InitProperties[1].vValue.bstrVal = SysAllocString(L"MyTest"); 21 InitProperties[1].dwOptions = DBPROPOPTIONS_REQUIRED; 22 InitProperties[1].colid = DB_NULLID; 23 24 //指定身份验证方式为集成安全模式“SSPI” 25 InitProperties[2].dwPropertyID = DBPROP_AUTH_INTEGRATED; 26 InitProperties[2].vValue.vt = VT_BSTR; 27 InitProperties[2].vValue.bstrVal = SysAllocString(L"SSPI"); 28 InitProperties[2].dwOptions = DBPROPOPTIONS_REQUIRED; 29 InitProperties[2].colid = DB_NULLID; 30 31 //创建一个GUID为DBPROPSET_DBINIT的属性集合,这也是初始化连接时需要的唯一一个属性集合 32 rgInitPropSet[0].guidPropertySet = DBPROPSET_DBINIT; 33 rgInitPropSet[0].cProperties = 4; 34 rgInitPropSet[0].rgProperties = InitProperties; 35 36 //得到数据库初始化的属性接口 37 IDBProperties* pIDBProperties = NULL; 38 hr = pIDBInitialize->QueryInterface(IID_IDBProperties, (void **)&pIDBProperties); 39 if (FAILED(hr)) 40 {//无法得到IDBProperties接口,详细的错误信息可以使用IerrorRecords接口得到 41 return FALSE; 42 } 43 44 hr = pIDBProperties->SetProperties(1, rgInitPropSet); 45 if (FAILED(hr)) 46 {//设置属性失败 47 return -1; 48 } 49 50 //属性一但设置完成,相应的接口就可以释放了 51 pIDBProperties->Release(); 52 53 //根据指定的属性连接到数据库 54 pIDBInitialize->Initialize();
在OLEDB中也可以通过IDataInitialize接口的GetDataSource方法来实现,例子如下:
1 IDataInitialize* pIDataInitialize = NULL; 2 CoCreateInstance(CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER, 3 IID_IDataInitialize,(void**)&pIDataInitialize); 4 5 IDBInitialize *pIDBInitialize = NULL; 6 //使用连接字符串得到一个IDBInitialize接口 7 pIDataInitialize->GetDataSource(NULL, CLSCTX_INPROC_SERVER, 8 L"Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=D:\\My Document\\db1.mdb;Mode=Share Deny None;", __uuidof(IDBInitialize), 9 IUnknown**)&pIDBInitialize); 10 11 //连接到数据库 12 pIDBInitialize ->Initialize();
二,创建事务对象
在创建了连接对象之后,我们需要创建一个事务对象。值得注意的是,一个连接对象可以与多个事务对象相对应。
最常用的一个事务对象接口是IOpenRowset,用于打开一个结果集。以下代码显示了如何从一个数据连接对象创建一个IOpenRowset接口。
1 IOpenRowset* pIOpenRowSet = NULL; 2 IDBCreateSession * pIDBCreateSession = NULL; 3 4 //首先从连接对象接口找到等价的CreateSession接口 5 pIDBInitialize->QueryInterface(IID_IDBCreateSession, (void**)&pIDBCreateSession)); 6 7 //创建一个IOpenRowset接口 8 pIDBCreateSession->CreateSession( 9 NULL, 10 IID_IOpenRowset, 11 (Iunknown**)&pIOpenRowSet)); 12 13 //与IDBInitialize等价的IDBCreateSession可以释放了,需要时再Query出来就行了 14 if( pIDBCreateSession ) 15 { 16 pIDBCreateSession->Release(); 17 }
以上代码通过创建IOpenRowset接口的方法创建了一个Session对象,其他的Session对象的接口可以通过QueryInterface来得到。(关于接口等价性、Session对象的接口列表,详见原文:http://gamebabyrocksun.blog.163.com/blog/static/571534632008101783231453/)
值得注意的是Session对象有个IGetDataSource接口,可以通过Session对象来找回原先的数据连接对象。你可以在创建了IOpenRowset接口之后丢掉之前创建的所有接口,甚至是数据连接对象的接口,而通过IGetDataSource接口则可以方便的找回数据连接对象的接口,以下代码演示了这一过程:
1 IGetDataSource* pIGetDataSource = NULL; 2 IDBInitialize * pIDBInitialize = NULL; 3 4 pIOpenRowset->QueryInterface( IID_IGetDataSource, 5 (void**) &pIGetDataSource ); 6 7 pIGetDataSource-> GetDataSource ( 8 IID_IDBInitialize,(IUnknown**)&pIDBInitialize);
如同数据连接对象,Session对象也有很多属性。你可以通过Query出ISessionProperties接口再创建一个属性集并对其进行设置,以下代码演示了这一过程:
1 //注意Session对象只有一个属性集合的一个属性,就是并发级别 2 //并发级别是控制数据库并发操作的关键,在OLEDB中它是一系列值的位或结果 3 //属性: 4 5 SessionProperties[0].dwPropertyID = DBPROP_SESS_AUTOCOMMITISOLEVELS; 6 7 SessionProperties[0].vValue.vt = VT_I4; 8 SessionProperties[0].vValue.lVal= DBPROPVAL_TI_READCOMMITTED; 9 SessionProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED; 10 SessionProperties[0].colid = DB_NULLID; 11 12 //创建一个GUID为DBPROPSET_SESSION的属性集合 13 rgSessionPropSet[0].guidPropertySet = DBPROPSET_SESSION; 14 rgSessionPropSet[0].cProperties = 1; 15 rgSessionPropSet[0].rgProperties = SessionProperties; 16 17 18 19 ISessionProperties* pISessionProperties = NULL; 20 //找到接口 21 pIOpenRowset->QueryInterface( IID_ISessionProperties, (void**) &pISessionProperties ); 22 23 //设置属性 24 pISessionProperties->SetProperties(1,rgSessionPropSet);
在Session对象的诸多接口中,如果我们要使用SQL语句来操作数据源,那么我们经常使用的一个接口就是IDBCreateCommand.这个接口并不是强制实现的。这是因为:OLEDB的目标就是无论应用系统要使用何种数据(结构化的如数据库、半结构化的如XML,无结构化的如互联网),它都能轻松胜任,而你无需再去寻找别的接口。大多数的数据源都提供了这个接口,以便对数据库进行SQL操作。
三,Command对象及SQL语句处理
1,Command对象全貌:
1 CoType TCommand 2 { 3 [mandatory] interface IAccessor; 4 [mandatory] interface IColumnsInfo; 5 [mandatory] interface ICommand; 6 [mandatory] interface ICommandProperties; 7 [mandatory] interface ICommandText; 8 [mandatory] interface IConvertType; 9 [optional] interface IColumnsRowset; 10 [optional] interface ICommandPersist; 11 [optional] interface ICommandPrepare; 12 [optional] interface ICommandWithParameters; 13 [optional] interface ISupportErrorInfo; 14 [optional] interface ICommandStream; 15 }
2,创建Command对象。
通常我们使用IDBCreateCommand事务接口来创建Command对象,也可以直接从IDBCreateSession接口创建。以下代码显示了从连接数据库到创建Command对象的完整过程:
1 //1、一大堆头文件和定义,作为前面例子代码的一个相对完整的总结 2 3 #define COM_NO_WINDOWS_H //如果已经包含了Windows.h或不使用其他Windows库函数时 4 #define DBINITCONSTANTS 5 #define INITGUID 6 #define OLEDBVER 0x0250 7 8 #include "oledb.h" // OLE DB Header 9 #include "oledberr.h" // OLE DB Errors 10 #include "msdasc.h" // OLE DB Service Component header 11 #include "msdaguid.h" // OLE DB Root Enumerator 12 #include "msdasql.h" // MSDASQL - Default provider 13 int main() 14 { 15 CoInitialize(NULL); 16 //2、创建IDataInitialize接口 17 IDataInitialize* pIDataInitialize = NULL; 18 CoCreateInstance(CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER, 19 IID_IDataInitialize,(void**)&pIDataInitialize); 20 21 IDBProperties *pIDBProperties = NULL; 22 IDBInitialize *pIDBInitialize = NULL; 23 24 //2、直接创建IDBProperties接口 25 pIDataInitialize->CreateDBInstance(CLSID_MSDASQL, NULL, 26 CLSCTX_INPROC_SERVER, NULL, IID_IDBProperties, 27 (IUnknown**)&pIDBProperties); 28 29 //3、定义属性集合,属性(略),设置属性 30 ...... 31 //4、连接到数据源 32 pIDBProperties->QueryInterface(IID_IDBInitialize,(void**)&pIDBInitialize); 33 pIDBInitialize->Initialize(); 34 35 //5、得到事务对象 36 IDBCreateSession* pIDBCreateSession = NULL; 37 pIDBInitialize->QueryInterface(IID_IDBCreateSession,(void**)& pIDBCreateSession); 38 39 //注意这次直接创建了IDBCreateCommand对象作为事务对象,注意一般情况下因为这个接口是个可选接口,所以直接创建有可能会失败,所以要检验返回值 40 //在这一系列文章中我省略这些检验性的操作,在实际的代码中一定要包含丰富的错误处理代码,有关错误处理的话题我将在以后的专门文章中详细阐述,这里先 41 //聚焦于我们所关心的问题 42 43 IDBCreateCommand* pIDBCreateCommand = NULL; 44 pIDBCreateSession->CreateSession(NULL,IID_IDBCreateCommand,(IUnknown**)&pIDBCreateCommand); 45 46 //6、创建Command对象,我们直接创建ICommandText接口 47 ICommandText* pICommandText = NULL; 48 pIDBCreateCommand->CreateCommand(NULL,IID_ICommandText,(IUnknown**)&pICommandText); 49 50 ...... 51 }
在Command对象中,ICommandText接口实际是从ICommand接口派生而来的,因此,ICommandText接口实际包括了ICommand接口的全部方法。ICommandText接口有如下方法:
1 HRESULT SetCommandText(REFGUID rguidDialect,LPCOLESTR pwszCommand); 2 HRESULT GetCommandText(GUID* pguidDialect,LPOLESTR* ppwszCommand); 3 4 //以下的方法实际也是ICommand的方法: 5 6 HRESULT Cancel(); 7 HRESULT Execute(IUnknown* pUnkOuter,REFIID riid,DBPARAMS* pParams, 8 DBROWCOUNT *pcRowsAffected,IUnknown** ppRowset); 9 HRESULT GetDBSession(REFIID riid,IUnknown** ppSession);
以下语法显示了如果设置和执行一个SQL语句:
1 TCHAR* pSQL = _T("Select * From SomeTable"); 2 3 pICommandText->SetCommandText(DBGUID_DEFAULT,pSQL); 4 IRowset* pIRowset = NULL; 5 6 pICommandText->Execute(NULL,IID_IRowset,NULL,NULL,(IUnknown**)&pIRowset);
以下方法演示了如果一次执行多条SQL语句:
1 TCHAR* pSQL = _T("Select * From SomeTable1\nSelect * From SomeTable2"); 2 3 pICommandText->SetCommandText(DBGUID_DEFAULT,pSQL); 4 5 IMultipleResults* pIMultipleResults = NULL; 6 7 pICommandText->Execute(NULL,IID_IMultipleResults,NULL,NULL , (IUnknown**)& pIMultipleResults);
MultipleResults对象只有IMultipleResults这一个接口,可以通过其得到多个结果集对象及其接口(IRowset)。一般情况下,作为自己对OLEDB的封装或是应用,都应用使用IMultipleResults接口,而不是直接使用IRowset。这样做的好处是,你既可以一次只执行一条SQL语句,也可以一次执行多条SQL语句。以下代码显示了这一特性的使用:
1 IMultipleResults* pIMultipleResults = NULL; 2 pICommandText->Execute(NULL,IID_IMultipleResults,NULL,NULL,(IUnknown**)& pIMultipleResults); 3 4 IRowset* pIRowset = NULL; 5 DBROWCOUNT cRowsAffected = 0;//注意不要被这个参数迷惑大多数情况下它是没用的,并不能通过它知道结果集中实际包含多少行。循环处理每一个结果集,当然这需要你起码知道你执行SQL语句的顺序 6 while( S_OK == pIMultipleResults->GetResult(NULL, 7 DBRESULTFLAG_DEFAULT, 8 IID_IRowset, 9 cRowsAffected, 10 (IUnknown**)&pIRowset) ) 11 { 12 ......//处理每一个IRowset 13 pIRowset->Release(); 14 pIRowset = NULL; 15 }
Command对象也有自己的属性集,其属性会影响因执行SQL语句而得到的结果集对象的属性(这不同于先前的两个对象),比如结果集是否可以修改,是否可以插入,是否可以更新等等。此外,需要注意的是,一个事务对象可以创建n个命令对象。以下代码显示了如果设置命令对象(其实是结果集对象)的属性:
1 DBPROPSET ps[1]; 2 DBPROP prop[2]; 3 4 prop[0].dwPropertyID = DBPROP_UPDATABILITY; 5 prop[0].vValue.vt = VT_I4; 6 prop[0].vValue.lVal=DBPROPVAL_UP_CHANGE //打开Update属性 7 |DBPROPVAL_UP_DELETE //打开Delete属性 8 |DBPROPVAL_UP_INSERT; //打开Insert属性 9 10 prop[0].dwOptions = DBPROPOPTIONS_REQUIRED; 11 prop[0].colid = DB_NULLID; 12 ps[0].guidPropertySet = DBPROPSET_ROWSET; //注意属性集合的名称 13 ps[0].cProperties = 1; 14 ps[0].rgProperties = prop; 15 16 ICommandProperties * pICommandProperties = NULL; 17 pICommandText->QueryInterface(IID_ICommandProperties, 18 (void**)& pICommandProperties); 19 pICommandProperties->SetProperties(1,ps);//注意必须在Execute前设定属性 20 21 IRowset* pIRowset = NULL; 22 pICommandText->Execute(NULL,IID_IRowset,NULL,NULL,(IUnknown**)&pIRowset)
以上代码演示了如何打开五个带有更新、插入、删除属性的结果集。这样,我们可以直接利用结果集对象的相关方法来修改数据并提交,同时可以绕过使用等价的SQL语句(UPDATE, DELETE, INSERT等),而是使用纯粹代码的方式修改数据并提交到数据库,这种方式要比直接使用SQL语句更加高效。
四,处理结果集
1,关于Rowset和MutipleResults对象的接口列表,可以参见原文:http://gamebabyrocksun.blog.163.com/blog/static/57153463200811882756317/
在Rowset对象中,我们经常使用的接口就是IRowset, IColumnInfo和IAccessor。通过这几个接口,才可以访问到我们查询得到的数据结果。这个过程通常很复杂。
2,得到结果集:
pICommandText->Execute(NULL,IID_IRowset,NULL,NULL (IUnknown**)&pIRowset)
3,得到列信息:
通过列信息可以知道结果集的完整数据结果,这为后续的创建访问器、准备数据缓冲奠定了基础。多数时候,在SQL的查询语句是是没有关于数据结构的信息的,这个信息是隐含在数据库中的,而我们查询得到结果集时就必须要知道这个数据结构的信息,否则对数据的访问就无从谈起。这个过程通过IColumnsInfo接口实现。以下代码演示了如何得到列信息:
1 IColumnsInfo * pIColumnsInfo = NULL; 2 ULONG cColumns = 0; 3 DBCOLUMNINFO * rgColumnInfo = NULL; 4 LPWSTR pStringBuffer = NULL; 5 6 HRESULT hr = pIRowset->QueryInterface(IID_IColumnsInfo,(void**)&pIColumnsInfo)); 7 8 hr = pIColumnsInfo->GetColumnInfo(&cColumns, &rgColumnInfo, &pStringBuffer)); 9 10 //如果成功了,那么rgColumnInfo中已经包含了一个关于列信息数据结构的数组, 11 //数组元素的个数即cColumns 也就是最终的列数 12 //使用完毕后释放所有的资源及接口 13 CoTaskMemFree(rgColumnInfo); 14 CoTaskMemFree(pStringBuffer); 15 if( NULL != pIColumnsInfo ) 16 { 17 pIColumnsInfo->Release(); 18 }
这里使用了CoTaskMemFree这个COM库函数来完成对rgColumnInfo和pStringBuffer内存的释放。注意,在OLEDB的帮助文档中说要使用IMalloc接口的Free方法来释放。其实二者是等价的。在使用CoTaskMemFree这个函数时,并不需要检查被释放的指针是否为空,它内部有检查是否为空的机制,直接调用即可。
DBCOLUMNINFO这个结构的原型如下:
1 typedef struct tagDBCOLUMNINFO 2 { 3 LPOLESTR pwszName; 4 ITypeInfo *pTypeInfo; 5 DBORDINAL iOrdinal; 6 DBCOLUMNFLAGS dwFlags; 7 DBLENGTH ulColumnSize; 8 DBTYPE wType; 9 BYTE bPrecision; 10 BYTE bScale; 11 DBID columnid; 12 }DBCOLUMNINFO;
pswzName:字段名称,为UNICODE字符串。如果查询中没有明确为列指定名称时,这个字段为空,即没有列名。
pTypeInfo:保留接口,一般为NULL。
iOrdinal:字段在结果集中的序号,即表示该字段是结果集的第几列。从1开始,序号0是为一些特殊用途保留的(比如结果集的书签功能)。
dwFlags:描述了列的状态。其类型为DBCOLUMNFLAGS,是个枚举类型,在MSDN中可以找到详细描述。
ulColumnSize:列的大小,单位为字节。需要注意的是,这个大小仅对字符型列(即wType = DBTYPE_STR或wType = DBTYPE_WSTR)有意义。而对于其他类型的字段则设置为一个~0值(即FFFFFFFF)。
wType:结果集列的数据类型。OLEDB中定义了完整的被支持的数据类型,此处将所有类型含义及欺直列出如下:
1 enum DBTYPEENUM 2 { 3 // The following values exactly match VARENUM 4 // in Automation and may be used in VARIANT. 5 DBTYPE_EMPTY = 0, 6 DBTYPE_NULL = 1, 7 DBTYPE_I2 = 2, 8 DBTYPE_I4 = 3, 9 DBTYPE_R4 = 4, 10 DBTYPE_R8 = 5, 11 DBTYPE_CY = 6, 12 DBTYPE_DATE = 7, 13 DBTYPE_BSTR = 8, 14 DBTYPE_IDISPATCH = 9, 15 DBTYPE_ERROR = 10, 16 DBTYPE_BOOL = 11, 17 DBTYPE_VARIANT = 12, 18 DBTYPE_IUNKNOWN = 13, 19 DBTYPE_DECIMAL = 14, 20 DBTYPE_UI1 = 17, 21 DBTYPE_ARRAY = 0x2000, 22 DBTYPE_BYREF = 0x4000, 23 DBTYPE_I1 = 16, 24 DBTYPE_UI2 = 18, 25 DBTYPE_UI4 = 19, 26 27 // The following values exactly match VARENUM in Automation but cannot be used in VARIANT. 28 DBTYPE_I8 = 20, 29 DBTYPE_UI8 = 21, 30 DBTYPE_GUID = 72, 31 DBTYPE_VECTOR = 0x1000, 32 DBTYPE_FILETIME = 64, 33 DBTYPE_RESERVED = 0x8000, 34 35 // The following values are not in VARENUM in OLE. 36 DBTYPE_BYTES = 128, 37 DBTYPE_STR = 129, 38 DBTYPE_WSTR = 130, 39 DBTYPE_NUMERIC = 131, 40 DBTYPE_UDT = 132, 41 DBTYPE_DBDATE = 133, 42 DBTYPE_DBTIME = 134, 43 DBTYPE_DBTIMESTAMP = 135 44 DBTYPE_HCHAPTER = 136 45 DBTYPE_PROPVARIANT = 138, 46 DBTYPE_VARNUMERIC = 139 47 };
需要特别注意的是,这里的数据类型不是数据库支持的数据类型,或者说并不是所有的数据库都支持这些所有的数据类型,这仅是一个所有可能数据类型的全部概括,当然也有一些数据库中的类型并不在这个列表中,这时往往数据库对应的OLEDB接口提供程序都做了很好的转换,已经转换成了这个列表中所具有的类型,因此不用担心会碰到不支持的数据类型。
bPrecision:精度。
bScale:小数位数。
columnid:该列在数据库系统字典表中的id号。
4,创建绑定:
绑定是OLEDB中最重要最核心的概念之一,它是我们访问数据的关键操作。对其最简单的理解就是:安排得到数据的内存摆放方式,并将这一方式告诉数据提供者,让它按要求将数据摆放到我们指定的内存中。为此,我们就需要自己创建一个被称作DBBINDING的数组,该结构体声明如下:
1 typedef struct tagDBBINDING 2 { 3 DBORDINAL iOrdinal; 4 DBBYTEOFFSET obValue; 5 DBBYTEOFFSET obLength; 6 DBBYTEOFFSET obStatus; 7 ITypeInfo *pTypeInfo; 8 DBOBJECT *pObject; 9 DBBINDEXT *pBindExt; 10 DBPART dwPart; 11 DBMEMOWNER dwMemOwner; 12 DBPARAMIO eParamIO; 13 DBLENGTH cbMaxLen; 14 DWORD dwFlags; 15 DBTYPE wType; 16 BYTE bPrecision; 17 BYTE bScale; 18 } DBBINDING;
可以看出,这个结构和前面的DBCOLUMNINFO结构很相似。其实它们的字段大多数确实是一致的,甚至可以直接使用DBCOLUMNINFO对DBBINDING进行赋值。但是二者字段的含义是完全不同的。首先DBCOLUMNINFO是数据提供者给你的信息,它是固定的,对相同的查询来说,列总是相同的,因此数据提供者返回的DBCOLUMNINFO数组也是固定的。而DBBINDING是你作为数据消费者创建之后给数据提供者的一个结构数组,它的内容完全有你来控制。通过这个结构我们可以指定数据提供者最终将数据摆放成我们指定的格式,或者进行指定的数据类型转换。以下例子展示了如何完成这个过程:
1 ULONG cColumns; 2 DBCOLUMNINFO * rgColumnInfo = NULL; 3 LPWSTR pStringBuffer = NULL; 4 5 IColumnsInfo * pIColumnsInfo = NULL; 6 ULONG iCol; 7 ULONG dwOffset = 0; 8 DBBINDING * rgBindings = NULL; 9 10 pIRowset->QueryInterface(IID_IColumnsInfo,(void**)&pIColumnsInfo)); 11 pIColumnsInfo->GetColumnInfo(&cColumns, &rgColumnInfo,&pStringBuffer)); 12 13 rgBindings = (DBBINDING*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,cColumns * sizeof(DBBINDING)); 14 for( iCol = 0; iCol < cColumns; iCol++ ) 15 { 16 rgBindings[iCol].iOrdinal = rgColumnInfo[iCol].iOrdinal; 17 rgBindings[iCol].dwPart = DBPART_VALUE|DBPART_LENGTH|DBPART_STATUS; 18 rgBindings[iCol].obStatus = dwOffset; 19 rgBindings[iCol].obLength = dwOffset + sizeof(DBSTATUS); 20 rgBindings[iCol].obValue = dwOffset+sizeof(DBSTATUS)+sizeof(ULONG); 21 rgBindings[iCol].dwMemOwner = DBMEMOWNER_CLIENTOWNED; 22 rgBindings[iCol].eParamIO = DBPARAMIO_NOTPARAM; 23 rgBindings[iCol].bPrecision = rgColumnInfo[iCol].bPrecision; 24 rgBindings[iCol].bScale = rgColumnInfo[iCol].bScale; 25 rgBindings[iCol].wType = rgColumnInfo[iCol].wType; 26 rgBindings[iCol].cbMaxLen = rgColumnInfo[iCol].ulColumnSize; 27 dwOffset = rgBindings[iCol].cbMaxLen + rgBindings[iCol].obValue; 28 dwOffset = ROUNDUP(dwOffset); 29 } 30 31 CoTaskMemFree(rgColumnInfo); 32 CoTaskMemFree(pStringBuffer); 33 34 if( pIColumnsInfo ) 35 { 36 pIColumnsInfo->Release(); 37 }
需要注意的是用红色标的四行。其中第一句指明数据提供者最终提交数据时必须包含的信息,这里指定了列值(DBPART_VALUE),长度(DBPART_LENGTH)和状态(DBPART_STATUS)共3个信息,列值不用说就是要放字段的最终结果值,长度就是这个字段占用的字节长度,列状态中将存放的是值的状态比如是否为空等。
后面3句则明确的指出了以上三个信息在内存中摆放的偏移位置,通常按习惯,我们要求数据提供者先摆放状态,再摆放长度,最后摆放数据。
5,创建访问器:
有了绑定结构之后,接下来的工作就是通知数据提供者按照我们的要求对数据进行“格式化”。这个过程就是创建一个访问器。创建访问器就要使用IAccessor接口,同样这个接口也是从IRowset查询得来,代表访问器的标志则是一个类型为HACCESSOR的句柄。以下代码演示了如果创建一个访问器:
1 HACCESSOR * phAccessor, 2 IAccessor * pIAccessor = NULL; 3 4 pIRowset->QueryInterface(IID_IAccessor,(void**)&pIAccessor)); 5 pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA,cColumns,rgBindings,0,phAccessor,NULL)); 6 if( pIAccessor ) 7 { 8 pIAccessor->Release(); 9 }
在这段代码中,我们最后直接释放了IAccessor接口,表示创建了访问器后这个接口也就没有用了。如果需要我们其实也可以随时从同一IRowset接口再查询出这个接口。因为这两个接口所表示的结果集对象是同一个。
6,得到数据:
以下代码演示了得到数据的过程:
1 void * pData = NULL; 2 ULONG cRowsObtained; 3 HROW * rghRows = NULL; 4 ULONG iRow; 5 LONG cRows = 10;//一次读取10行 6 void * pCurData; 7 8 //分配cRows行数据的缓冲,然后反复读取cRows行到这里,然后逐行处理之 9 pData = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY, dwOffset * cRows); 10 while( S_OK == pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0,cRows,&cRowsObtained, &rghRows)) ) 11 {//循环读取数据,每次循环默认读取cRows行,实际读取到cRowsObtained行 12 for( iRow = 0; iRow < cRowsObtained; iRow++ ) 13 { 14 pCurData = (BYTE*)pData + (dwOffset * iRow); 15 pIRowset->GetData( rghRows[iRow],hAccessor,pCurData)); 16 //pCurData中已经包含了结果数据,显示或者进行处理 17 ...... 18 } 19 if( cRowsObtained ) 20 {//释放行句柄数组 21 pIRowset->ReleaseRows(cRowsObtained,rghRows,NULL,NULL,NULL)); 22 } 23 CoTaskMemFree(rghRows); 24 rghRows = NULL; 25 } 26 HeapFree(GetProcessHeap(),0,pData); 27 if( pIRowset ) 28 { 29 pIRowset->Release(); 30 }
代码中,HROW表示行的句柄,这里使用的是HROW的数据,读出了多少行,数组中应有多少行的句柄,然后再使用句柄调用GetData将数据读到指定的内存位置。最后每次都释放了这个数据,因为数据已经诗刊了我们分配的缓存中,这个句柄数组也就没有什么意义了。最终数据读取完毕,我们也就释放了IRowset接口。
EOF。