使用Ado访问IRowset的数据

昨天使用OLEDB原始的方式列出了系统里安装的所有OLEDB Provider,要绑定啊啥的实在很麻烦,所以就想能不能用ADO来搞。昨天弄了一下无果,今天接着,把它弄出来了。

ADO有一个Rowset对象,看起来是不需要Binding可以直接获取数据的。于是昨天就找啊找,发现确实可以从IRowset接口构造Recordset对象的。但是苦于MSDN上对于这方面的介绍,只有关于对象的,却几乎没有关于对象类型的。这样子的话我能想到的就只能通过IDispatch来调用。因为不太想这么做,所以最终去看了msado15.tlh头文件,发现了点线索。

Recordset对象有一个Recordset类型,原先我创建对象的时候用的是

ADODB::Recordset* adoRecordset;
CoCreateInstance(__uuidof(ADODB::Recordset), NULL, CLSCTX_INPROC_SERVER, __uuidof(ADODB::Recordset), (LPVOID*) &adoRecordset);

但是不仅仅是这里失败,连后面在它上面的函数调用都失败。函数调用的地方说ADODB::Recordset是不完整的类型,无法在上面调用方法。看头文件,发现有定义的是一个_Recordset结构体。前面多了个下划线。好吧既然这样那我就

ADODB::_Recordset* adoRecordset;
CoCreateInstance(__uuidof(ADODB::_Recordset), NULL, CLSCTX_INPROC_SERVER, __uuidof(ADODB::_Recordset), (LPVOID*) &adoRecordset);

虽然编译通过了,但是这个CoCreateInstance华丽丽地返回了REGDB_E_CLASSNOTREG。看来类还是要用没有下划线那个,毕竟MSDN里名字没有下划线……。整了整,变成下面这样,可以成功创建对象了

ADODB::_Recordset* adoRecordset;
CoCreateInstance(__uuidof(ADODB::Recordset), NULL, CLSCTX_INPROC_SERVER, __uuidof(ADODB::_Recordset), (LPVOID*) &adoRecordset);

(P.S. 后来发现看头文件的话,可以看出Recordset有注释Coclass,而_Recordset注释是interface

然后接下来,要用ADORecordsetConstruction接口里的Rowset属性来把IRowset接口的数据对象塞进去。就直接调用QueryInterface然后put_Rowset,这倒是很顺利

ADODB::ADORecordsetConstruction* adoRecordsetContructor;
adoRecordset->QueryInterface(__uuidof(ADODB::ADORecordsetConstruction), (LPVOID*) &adoRecordsetContructor);
adoRecordsetContructor->put_Rowset(pRowset);
adoRecordsetContructor->Release();

往后查看Recordset对象的方法集,一眼看到对我有用的就是MoveNext和GetRows,还有EOF属性(注:这个EOF属性在导入DLL生成TLH文件的时候要重新命名一下,不然会报错)。这个GetRows好眼熟,就是昨天OLEDB里拿到一堆HROW然后可以获取数据的那个。那么看名字,它是要我调用MoveNext然后检查EOF然后获取数据,之后循环咯?MoveNext看它的说明是没有参数。EOF是VARIANT_BOOL型,这种类型还有点那啥那啥啥因为它的定义是TRUE的时候0xFFFF、FALSE的时候0,我在实际操作的时候和0xFFFF比较似乎还比不出来,大概是数据类型不一样(直接写0xFFFF默认是32位而VARIANT_BOOL是16位,转换的时候哪里错了可能)。然后麻烦的是GetRows,MSDN里没有给出返回值类型,就说到返回的是一个二维数组。参数的话,第二个参数至今没明白,最后一个参数就是送一个VARIANT型的字符串或者数组进去,指明要返回什么列。这返回值就是不知道是怎么回事,这种参考看的真的各种痛苦,于是看它例子,例子里赫然是_variant_t(其实后来发现,看头文件就知道是这个类型了……但是不怎么想翻头文件啊orz)。

根据研究,GetRows返回的数组大概长这样:

[0][0] 记录1,字段1 [0][1] 记录2,字段1 [0][2] 记录3,字段1
[1][0] 记录1,字段2 [1][1] 记录2,字段2 [1][2] 记录3,字段2
[2][0] 记录1,字段3 [2][1] 记录1,字段3 [2][2] 记录3,字段3

这么画出来,总觉得哪里微妙地有点不习惯……怎么不是一行一个记录而是一列一个记录。

于是乎,输出数据就有了这样子的代码(EOF我重命名为adoEOF了)

adoRecordset->MoveNext();
for(;;) {
    VARIANT_BOOL vbEof;
    adoRecordset->get_adoEOF(&vbEof);
    if (vbEof != FALSE) break;

    VARIANT data;
            
    data = adoRecordset->GetRows(1);

    LONG nIndexes[2];
    nIndexes[0] = 0;
    nIndexes[1] = 0;

    VARIANT result;
    result.vt = VT_BSTR;

    SafeArrayGetElement(data.parray, nIndexes, &result);
    wprintf(L"%s\n", result.bstrVal);

    nIndexes[0] = 2;

    SafeArrayGetElement(data.parray, nIndexes, &result);
    wprintf(L"%s\n\n", result.bstrVal);

    VariantClear(&result);
    VariantClear(&data);

    adoRecordset->MoveNext();
}

结果就是,崩。单步跟踪,发现GetRows调用完之后,data里可以获取到类型是数组,但是却没有数据。这非常诡异,仔细对比MSDN给的例子,对比出来发现它用的类型_variant_t和我写VARIANT不同。我觉得这两个是一样的东西啊,但是为什么我改成_variant_t以后就可以了呢。去查_variant_t的定义,发现了我被坑的地方,在comutil.h中有这样的代码

inline _variant_t::~_variant_t() throw()
{
    ::VariantClear(this);
}

而GetRows的返回类型又是_variant_t,所以我的VARIANT其实获取到的是一个已经析构了的_variant_t的内部数据(好了嘛,我只是想试着用一下、理解一下它的原始接口,然后再接触这些util会掌握得比较好而已了啦><)。

知道了这个问题以后,就把GetRows(1)改成GetRows(1).Detach()了(死不悔改(殴

data = adoRecordset->GetRows(1).Detach();

这下程序可以运行了,但是输出的还是不对。到底是哪里不对呢,好像比其他地方少了不少,大概有一半的样子。是不是和ADO.net不一样,一开始不需要DataReader.Read一下呢,我就把for前面的那个MoveNext给删了,运行,崩,崩在MoveNext。

按理来说我都有检查EOF,不会出现到尾端了还在继续MoveNext的问题才对。回想起OLEDB里GetNextRows的操作,好像只有GetNextRows而没有MoveNext?于是其实不需要MoveNext才是对的?那我就先把MoveNext去掉,看看会不会死循环吧。

然后结果是不死循环,获取数据一切正常,囧。

虽然总觉得还是略麻烦,不过不知道相对于OLEDB来说是不是已经简单不少了?对比一下,娘的,程序代码的行数似乎没见得有什么变少 –_,–

为了尝试一下MoveNext,又改了改代码,改为直接从Recordset的Fields属性里拿数据,而不是GetRows。试过了满足了总之略麻烦

for(;;) { //0xffff is true
	VARIANT_BOOL vbEof;
	adoRecordset->get_adoEOF(&vbEof);
	if (vbEof != FALSE) break;

	VARIANT data;
            
	ADODB::Fields* fields;
	ADODB::Field *f1, *f2;
	adoRecordset->get_Fields(&fields);
	VARIANT nIndex;
	VariantInit(&nIndex);
	nIndex.vt = VT_I2;
	nIndex.intVal = 0;
	fields->get_Item(nIndex, &f1);
	nIndex.intVal = 2;
	fields->get_Item(nIndex, &f2);
	fields->Release();
	VariantClear(&nIndex);

	VARIANT val1, val2;
	VariantInit(&val1);
	VariantInit(&val2);
	f1->get_Value(&val1);
	f2->get_Value(&val2);

	f1->Release();
	f2->Release();

	wprintf(L"%s\n", val1.bstrVal);
	wprintf(L"%s\n\n", val2.bstrVal);

	VariantClear(&val1);
	VariantClear(&val2);

	adoRecordset->MoveNext();
}

为什么VARIANT的类型是I2而不是I4?sa,我用I4它就不认,后面看了MSDN的例子才知道是I2。MSDN里对于这一块的解释实在是不够详细啊,至少没有Kernel32.dll里面大部分API讲得那么详细。

使用MoveNext和使用GetRows的代码都放上来了

发表评论