对,我还只是说数据访问。直接给我一个ISourcesRowSet,还没让我去连数据库去发SQL语句啥的。
今天突然想起以前好像在哪里有看到把Excel表格当作数据集来访问的东西。然后到了实验室就去找关于这方面的文章,找到使用ADO来做这样访问的方法。但是看了其中提到要用某个特定的Provider。我发现我只会查看所有ODBC数据提供程序的方法,但是OLEDB的却不知道应该怎么处理。于是就又上网搜搜方法,发现C#有个很简单很容易的方法,直接用如下简单的程序就能列出来
using System; using System.Data.OleDb; class Program { static void Main(string[] args) { OleDbDataReader rdr = OleDbEnumerator.GetRootEnumerator(); while (rdr.Read()) { Console.WriteLine(rdr[0]); Console.WriteLine(rdr[2]); Console.WriteLine(); } } }
但是这是C#的方法,我想知道C++怎么做。但是在MSDN理却没有找到GetRootEnumerator在非托管下应该怎么做。上网搜的话,也几乎全都是介绍如何在C#下搞的。既然是OLEDB,我认为最终还是调用OLEDB下的组件来实现。
于是我把这个EXE程序丢到OllyDBG里面,拦截CoCreateInstance系统调用,最终找到了个看起来和它相关的,其GUID是C8B522D0-5CF3-11CE-ADE5-00AA0044773D。到注册表里面找,找到了个叫“Microsoft OLE DB Root Enumerator”的东西。
赶快拿到MSDN里面搜索,果然有这个对象。分类在 Windows Desktop App Development –> Data Access and Storage –> Windows Data Access Components SDK –> Microsoft OLE DB –> OLE DB Programmer's Guide –> OLE DB Core –> OLE DB Core Components –> Root Enumerator Object 这里面。这页面里说到这个枚举所有OLEDB数据提供程序的类的ID是CLSID_OLEDB_ENUMERATOR。那么C++下调用CoCreateInstance应该可以创建出这个对象,然后实现同样的功能才对。
IUnknown* pEnum; CoCreateInstance(CLSID_OLEDB_ENUMERATOR, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (LPVOID*) &pEnum);
然后这个页面还有提到调用这个对象上的ISourcesRowset::GetSourcesRowset方法可以获取数据集。那么也就是说这个对象实现了ISourcesRowset接口。
ISourcesRowset* pSrcRowset; pEnum->QueryInterface(IID_ISourcesRowset, (LPVOID*)&pSrcRowset);
在MSDN找了ISourcesRowset有什么东西,看到就只有一个GetSourcesRowset方法,能返回一个包含数据的对象。但是死坑死坑的是它不告诉你这个对象是什么类型。……只有一个IUnknown我拿来干嘛了啦!
看着名字,Get的是一个Rowset,所以我就假定它会给我IRowset,代码拿去一运行,确实成功返回了IRowset接口的对象。
IRowset* pRowset; pSrcRowset->GetSourcesRowset(NULL, IID_IRowset, 0, NULL, (IUnknown**) &pRowset);
然后看IRowset提供的方法,噢噢很好,有GetNextRows和GetData。这不就是最标准的拿数据然后读数据吗,看来目的达到了。但是点进去看它这两个方法定义的时候,发现很恶心的东西又来了,
HRESULT GetData ( HROW hRow, HACCESSOR hAccessor, void *pData); HRESULT GetNextRows ( HCHAPTER hChapter, DBROWOFFSET lRowsOffset, DBROWCOUNT cRows, DBCOUNTITEM *pcRowsObtained, HROW **prghRows);
何?HROW,这是啥?HACCESSOR,这又是啥?
从GetNextRows可以看出,它要求送一个prghRows进去,那么就是会给你一堆HROW(既然名字都用Rows这个复数形式了),看函数说明,是二级指针的原因是方便决定这数组是它来分配还是你来分配。于是剩下的问题就在于这个HACCESSOR。
在MSDN给的某个代码示例里(Windows Desktop App Development –> Data Access and Storage –> Windows Data Access Components SDK –> Microsoft OLE DB –> OLE DB Programmer's Guide –> Introduction to OLE DB –> Getting and Setting Data (OLE DB) –> Binding and Accessor Example)看到,它那个HACCESSOR是通过某个IAccessor接口的对象来创建的。而这个IAccessor接口的对象,是从那个Rowset来的。……好吧那么我从我得到的Rowset里QueryInterface里一个这样接口的对象出来就有了吧
IAccessor* pAccessor; pRowset->QueryInterface(IID_IAccessor, (LPVOID*)&pAccessor);
之后这个IAccessor有一个CreateAccessor可以拿到HACCESSOR类型的东西,也有一个ReleaseAccessor一看就懂的东西。根据那个例子,接下来我要做的事情是绑定列和变量,那么我以后每次GetData它就会把列的数据存入我给的变量。我现在要获取的有两个列,于是弄一个2个元素的DBBINDING数组,然后给它赋值
DBBINDING arrBinding[2]; ZeroMemory(arrBinding, sizeof(arrBinding)); arrBinding[0].iOrdinal = 1; //下标从1开始 arrBinding[0].obValue = OFFSETOF(RowData, Name[0]); arrBinding[0].cbMaxLen = 512; arrBinding[0].dwPart = DBPART_VALUE; arrBinding[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED; arrBinding[0].wType = DBTYPE_WSTR; arrBinding[1].iOrdinal = 3; arrBinding[1].obValue = OFFSETOF(RowData, Desc[0]); arrBinding[1].cbMaxLen = 2048; arrBinding[1].dwPart = DBPART_VALUE; arrBinding[1].dwMemOwner = DBMEMOWNER_CLIENTOWNED; arrBinding[1].wType = DBTYPE_WSTR;
其中RowData和那个OFFSETOF是这样的
struct RowData { WCHAR Name[256]; WCHAR Desc[1024]; }; #define OFFSETOF(type,member) ((DWORD) &(((type*)0)->member))
这第一个列和第三个列,是从ISourcesRowset::GetSourcesRowset给的说明里得知的,我要其中的namehe和description,所以拿第一个列和第三个列。如果想要知道列信息,根据那个example,可以从Rowset里整一个IColumnsInfo出来。
然后把这样的绑定信息拿去创建HACCESSOR。
HACCESSOR hAccessor; pAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 2, arrBinding, sizeof(RowData), &hAccessor, NULL);
这样看来,要的东西齐了,可以拿来显示数据了。
for(;;) { HROW hRow[1]; HROW* phRow = hRow; DBCOUNTITEM nRow = 0; RowData rowData; hRet = pRowset->GetNextRows(DB_NULL_HCHAPTER, 0, 1, &nRow, &phRow); if (hRet == DB_S_ENDOFROWSET || nRow != 1) break; pRowset->GetData(hRow[0], hAccessor, &rowData); wprintf(L"%s\n", rowData.Name); wprintf(L"%s\n", rowData.Desc); wprintf(L"\n"); pRowset->ReleaseRows(1, hRow, NULL, NULL, NULL); }
用完以后把前面的那些什么对象一个个Release掉也没有关系了。哦对了COM对象都有引用计数的,那么其实可以不考虑间接引用,调用完自己需要的东西就可以Release对吧……?
于是ADO.net里那么简单的一点东西,用纯OLEDB的方式来访问,搞了这么多这么多这么麻烦的代码出来。累不爱
最后编译程序的时候,还接连报错。各种什么这个找不到那个找不到,因为很坑爹的是和那些API参考不同,这些什么什么东西的参考,在MSDN里面,至少没有每个参考页面最下面都告诉你在哪个头文件里,这类的事情orz 于是只好SDK的Include文件夹全部搜过去,找我要的东西。最后写成的代码
因为太麻烦了,所以我准备找找看有没有办法把这些数据啥的塞到ADO的对象里面去访问