好麻烦的OLEDB数据访问

对,我还只是说数据访问。直接给我一个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文件夹全部搜过去,找我要的东西。最后写成的代码

listAllOleDBProvider

因为太麻烦了,所以我准备找找看有没有办法把这些数据啥的塞到ADO的对象里面去访问

发表评论