昨天使用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的代码都放上来了