Sunday, March 20, 2005

 

Some notes on ADO

I.
我的ATL/ADO编程的曲折经历

作者:horris
出处:http://www.csdn.net/develop/article/14/14662.shtm

我在用VC6的ATL作一个组件,它内部通过ADO访问Access数据库。因为ADO本身也是一系列组件,因此,ATL项目要引入ADO类型库,我是用以下语句引入的(假设Windows安装在C盘):
#import "C:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace named_guids rename("EOF","adoEOF")
这是在微软的官方教材(1015 Mastering COM Development using Visual C++ 6.0)上讲的引用ADO的标准方法,教材上说,这样可以使你能使用最新的ADO版本。请注意,这是第一个麻烦所在。
Access数据库是在Access97上建立的,所以我用了Jet 3.51的OLE DB Provider,也就是说Connection串是:
“Provider=Microsoft.Jet.OLEDB.3.51;。。。”
这是第二个造成麻烦所在。
我的开发环境是Win2K,这是第三个麻烦所在。下面请听我慢慢道来。
好了,在 Win2K上调试一切正常。我一直很信任2K,因为在2K上调程序系统很稳定,另外速度好象比在98上快,最重要的是很多在98上用Debug跟踪不到的错误,在2K上都能跟踪到!我信心百倍地交给用户了。用户机器是Win98SE,另外装了Access2000。但是用户机器上报错:”Create ADODB.Recordset failed”!当然98没有这么智能,这个错误信息是我留了个心眼儿,在程序中让它报的:
_RecordsetPtr pRs=NULL;
if (pRs.CreateInstance(CComBSTR("ADODB.Recordset"))!=S_OK)
{
Error("Create ADODB.Recordset failed");
return E_FAIL;
}
98不会不支持ADO呀!我首先想到会不会是Access2000的问题?但是也不应该,虽然在Access2000执行界面不能修改Access97的数据库结构,但是因为Access2000是Jet4.0驱动,它应该向下兼容地呀!通过ADO是可以修改结构的,何况我并没有修改结构,我甚至连数据都没有修改!
翻箱倒柜地一通查,在MDAC 2。5的帮助文件中我找到了:从ADO2。1开始提供的JET4。0 OLE DB Provider将禁用某些JET3。5x的文件,该死的微软!我不得不把连接串改成了:
“Provider=Microsoft.Jet.OLEDB.4.0;。。。”
毕竟现在用户机器都升级到Office2000了,用Access97的人不多了。但是仍然报那个错!换到另一台装了WinMe的机器上安装运行,运行正常!这是怎么回事?我差点怀疑那个装98的机器系统有问题。于是我查它的注册表,HKEY_CLASSES_ROOT\CLSID下是有ADODB.Recordset的。在几乎绝望之际,我发现了这个:
#import msado15.dll生成的文件msado15.tlh里,_Recordset的IID是
"00000556-0000-0010-8000-00aa006d2ea4"
而那个倒霉98机器的注册表的HKEY_CLASSES_ROOT\Interface下没有这个IID!我赶紧用OLE View查看了98上的msado15.dll,却在里面看到了_Recordset接口,所有Interface,coclass应有尽有。有意思有意思!编了这么多年程序,让我长了记性:永远不要怀疑系统有问题、编译器有问题,永远要坚信是自已的程序的问题。幸亏我还算有点观察力,我发现这个98机器上的msado15.dll的_Recordset接口的IID是:
"00000555-0000-0010-8000-00aa006d2ea4"
看见没有,一个是556,一个是555,它们不是一个接口!
好,仔细看看OLE View为我揭示的msado15.dll:
_Recordset派生于Recordset20,Recordset20派生于Recordset15,Recordset15就差不多到根上了(怪不得文件名是msado15而不是msado20或别的),它们每个的IID都不一样。我又看了看2K上的msado15.dll, _Recordset派生于Recordset21,Recordset21派生于Recordset20,下面的派生树与98上的就一样了。我又注意到,2K的msado15.dll的library节的version属性是2.5,也就是说typelib版本是2.5,而98上的是2.1。(按:这段最有价值,可以看到作者是如何下手分析的)
我终于明白了,原来我开发用的ADO版本与用户机器上的用户版本不一样,开发用的是ADO2。5,而用户机器上的是ADO2。1。ADO 2。1版的Recordset命名为Recordset21,2。0版的Recordset命名为Recordset20,依此类推,而ADO总是把最新版的Recordset接口命名为_Recordset。所以在用VC的#import时,生成的_RecordsetPtr是msado15.dll支持的最高版的Recordset。
那么,我怎么能知道用户机器安装了哪个ADO版本呢?
在MSDN中没有直接的方式查找这方面的信息,或者说我无从下手。我只好用“搜索”功能。搜索“MDAC”查到的主题数大大超出我的想象—有500页之多!幸亏没查“ADO”,那样会更多。在看了三五个主题后,我有些头大了,微软的数据库存取技术的版本控制太混乱了!在咬牙坚持看完了不下十个主题后,终于理出了一些头绪。
首先,M$ Bless Me! 这个主题在搜索结果中比较靠前,使我及时了解了一些基本概念,能坚持看完后面的主题:
INFO: What are MDAC, DA SDK, ODBC, OLE DB, ADO, RDS, and ADO/MD?
ID: Q190463
MDAC是ODBC, OLE DB, ADO, RDS四类数据库存取技术的总称。比较象样的MDAC包是从版本1.5开始的。它包括ODBC 3.5, OLE DB 1.5, ADO 1.5, RDS 1.5。2.0的MDAC曾一度被命名为Data Access SDK 2.0,它包括ODBC 3.51, OLE DB 2.0, ADO 2.0, RDS 2.0。以后的ADO版本基本上和MDAC的版本一致。除了大版本外,还有象1.5b,1.5(PDC)等小版本,但是大版本的功能是差不多一样的。
好了,我关心的是怎样确定用户机器的ADO版本,然后才能知道要发布哪些文件。到用户机器上手工查看msado15.dll的Typelib版本总不是个办法。这个主题好象有点用:
HOWTO: Determine the Version of MDAC
ID: Q269490
不过这要下载一个Component Checker的软件,或者依靠一个并不可靠的注册表项。我更想知道:给定一个用户机器的软件配置,能确定它支持的ADO最小版本。
M$ Bless Me Again! 下面的主题又比较靠前:
INFO: Microsoft Data Access Components (MDAC) Release History
ID: Q231943
请看OLE DB/ADO的曲折发展进程和混乱的版本发布:
MDAC 1.5在以下产品中安装了Beta版:NT Options Pack(IIS 4)/IE4/Win98,正式发布是在08/01/1997的IE4,也就是说,Win98或是Win95+IE4可以保证ADO1.5的存在。而在NT内核的操作系统中,NT4/OP/IIS4也最少可以用1.5。
在07/01/1998的NT4的SP4中,包含了MDAC 2.0。这回NT内核超过了98内核。
在3/15/1999的IE5中,包含了MDAC 2.1,我们知道Win98SE是与IE5绑定的。这回98内核领先了。
4/1/1999,NT上的BackOffice 4.5赶了上来,支持MDAC 2.1了。
2/17/2000, Windows 2000来了个大一统,干脆绑定了MDAC 2.5。
呵呵,我用的是MSDN January 2001,后面的事情就查无出处了。我看过WinMe的缺省安装,是支持MDAC 2.5的,98内核与NT内核走到一起了。
后来发布的还有MDAC 2.6,我估计它是独立发布的。我有一台装XP的机器已经装上VS.net了,VS.net要求安装MDAC 2.7。我已分不清楚2.6和2.7哪个是与XP绑定的了。不过后面你将看到,对主要用ADO.Recordset对象的编程者来说,MDAC 2.5与2.6/2.7差别不大。
下面让我们看看ADO各版本中的Recordset对象都有哪些变化。
Recordset15没有完善的Clone和Resync方法,另外很可能不支持异步方法调用(asynchronous method)。这些都在Recordset20里实现了,Recordset20的Cancel方法支持终止异步方法调用。ADO 2.0还实现了Recordset的Persistance,不过只支持ADO专用的ADTG(Microsoft Advanced Data TableGram)格式。
Recordset21增加了Seek方法和Index属性。另外在Persistance方面还支持部分的XML格式。
从ADO 2.5起,Persistance得到了很大的完善。Recordset对象完全支持Persistance到XML格式,但是这依赖于Microst XML Parser,也就是msxml.dll,它从IE5开始提供。Recordset对象还可以Persistance到任何实现了IStream接口的对象,并且ADO 2.5还提供了Stream对象。因此,ADO 2.5的Recordset可以直接Persistance到IIS5(Windows 2000绑定)的ASP Response/Request对象,为ASP的数据存取编程提供了极大的方便。
ADO 2.5以后的2.6,2.7版本对Recordset的接口没有再做改动,所增强的是其他ADO对象,例如ADO 2.7中支持Command 2.7对象。对于主要使用Recordset对象的开发者来说,2.5与2.6,2.7区别不大。
好了,我班门弄斧地总结了ADO的发展历程和各版本的功能,下面该讨论一下如何发布ADO应用程序了。
需要说明的是,MDAC包不仅随操作系统和IE发布,它也经常做为一个单独的包发布,例如在PDC(Professional Developer’s Conference)上。它还随一些应用系统发布,如BackOffice,SQL Server等,尤其是它也随Visual Studio发布。VS6中包含MDAC 2.0,而VS6的SP3包含MDAC 2.1。不过这些不是我们开发者考虑的主要问题.我想如果你在你的软件安装需求中写上“本软件要求您安装了MDAC X.X”会使大多数用户看不懂,包括许多MCSE(BTW:我是MCSD,因为D在E之前,所以我有优越感)。“本软件要求您安装了VS6或者SQL Server”也经常会使用户感到手足无措,而“要求Win98SE/WinNT+IE5/Win2K”比较清晰明了。用户对自已用的操作系统和IE的版本是比较清楚的,这两样也是必装的,所以在确定用户机器的MDAC环境时,我觉得还是主要依赖于对这两样的判断为好。
MDAC包有一个安装程序mdac_typ.exe,可以把它加到你的应用程序的安装工程中去,当用户机器的ADO版本比你的要求低时用它安装高版本的MDAC。在VS6的安装盘中你可以找到这个程序。MSDN的这个主题可以提供帮助:
Redistributing MDAC
但是这要在安装工程中做很多文章,而且安装包将会变得很大,另外你还可能和M$惑上官司,如果你是用D版的话。所以我更愿意把我的程序对系统的要求降到最低,换句话说,我想让我的程序使用的ADO版本尽量低。怎么办呢?
首先我沮丧地想到要找一个Win98或Win95+IE4的机器,把它的msado15.dll复制过来#import,或者干脆在这上面开发。但是现在要找一个Win98第一版或Win95的机器已经很难了,而且在上面运行VS6是太别扭了。复制一个msado15.dll又很容易与开发机器上的搞混。忽然我想到VC6有一个头文件叫adoint.h,我猜它是ADO Interfaces的意思。在这里有ADO各对象、接口的定义!我打开了adoint.h,发现它的_Recordset的IID是:
"0000054F-0000-0010-8000-00AA006D2EA4"
再一查msado15.dll,这是Recordset20的IID,也就是说,VC6的adoint.h提供的是ADO 2.0的对象和接口。这很好理解,VS6自带的MDAC2.0。再看看支持ADO 2.0的最低操作系统:Win98SE/NT+SP4/2K/XP,这对大多数用户来说都满足的,所以按这个adoint.h的定义使用ADO很理想。
如果你还想用Smart Pointer(_com_ptr_t)的话,如_RecordsetPtr,那么就要把
#import "C:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace named_guids rename("EOF","adoEOF")
换成:
#include
#include

#include
_COM_SMARTPTR_TYPEDEF(_Connection, __uuidof(_Connection));
_COM_SMARTPTR_TYPEDEF(_Recordset, __uuidof(_Recordset));
_COM_SMARTPTR_TYPEDEF(Fields, __uuidof(Fields));
_COM_SMARTPTR_TYPEDEF(Field, __uuidof(Field));
其中#include 是可选的,它使你能用ADO内部的VC支持接口IADORecordBinding来处理记录数据,避开在VC中今人生烦的对VARIANT的处理,我比较喜欢用这个接口。
#include 是必须的。没有它,你会碰到一大堆编译错误,主要是“抽象类(abstract)的成员函数没有实现”之类的信息。
后面的_COM_SMARTPTR_TYPEDEF为你需要的接口指针定义Smart Pointer类型(_com_ptr_t的模板实现类XxxxPtr)。我列出的是最常用的接口。
有一点要注意:如果你用的是VS6的SP3,那么Adoint.h有可能是ADO 2.1的定义(我没有VS6 SP3,不能肯定)。
这里还有一点小麻烦,用这种方法生成的smart pointer只支持接口的原生方法和属性,例如你只能用put_CursorLocation/get_CursorLocation而不能用#import为你实现的PutCursorLocation/GetCursorLocation,#import实现的对方法的BSTR和VARIANT参数的包装(用_bstr_t和_variant_t类)也不能用了,你必须自已处理BSTR和VARIANT,这包括对SysAllocString/SysFreeString/VariantInit/VariantClear/VariantChangeType等的小心使用。不过如果你是用ATL开发的话,ATL的CComBSTR和CComVariant类可以起到_bstr_t和_variant_t同样的作用。
最后来看一下现在的大红人Visual Studio.net,如果你安装时选择了安装Platform SDK(缺省是不安装的),VC.net的adoint.h在VS.net安装目录的vc7\PlatformSDK\include下。看一下这个文件,_Recordset是2.5的,只有_Command是派生于Command25,也就是说,VC.net的adoint.h是支持ADO 2.7的(ADO 2.7的大多数接口与2.5相同,只有Command接口是新的)。因此,虽然VC.net是VS.net中唯一能开发Windows专有平台应用的工具,但是用它开发ADO应用要小心,它用的是ADO 2.7,如果用到了Command对象,那么你的应用只能在XP上用了!
可能有人会有这样的想法,不管开发时引用的是哪个版本的ADO,代码里不用_RecordsetPtr不就行了吗?比如用Recordset20Ptr,事实是这样也不行,因为#import生成的Recordset20Ptr代码中引用了_RecordsetPtr,不信你可以试试。
不揣浅陋,胡侃了这么多,请各位指正。

另外,CCBoy评述道

1.用MDAC的版本来评断NT或98的内核似乎有些不妥。
2.如果使用ATL/C++那么首选的应该是OLEDB而不是ADO
3.msado15.dll在MDAC的各个版本的接口都可能不同,这就如同msxml.dll在各个版本的情况一样,很混乱和没有规律。我想正确的方法应该是无论什么平台,你交付用户时都要带上你开发平台下那个ADO版本的mdac_typ.exe给用户。不然你需要写许多特别的代码判断用户机器上的版本是否合你所用。
总之ATL+ADO不是一个最优的搭配和组合。

II.
破解Access目前所有版本的密码

作者:ccrun
出处:http://www.ccrun.com/


关于Access97的密码破解,在很多的网站和杂志上都有过介绍。在这里我简单重复一下。

在mdb文件第0x42字节处的13个字节分别与0x86,0xfb,0xec,0x37,0x5d,0x44,0x9c,0xfa,0xc6,0x5e,0x28,0xe6,0x13异或后即可得到数据库的密码。但在Access 2000和2002的版本里密钥不再是固定的13个字节.而且加密的方式也有了变化。

经过ccrun用一下午的时间研究,终于将Access2000的加密方式搞清楚了。嘿嘿。在此将偶的心得发布。希望对大家有用,如果您发现我的理解有误,请来信告之我们。信箱:info@ccrun.com 版权虽然有没有都没关系,不过如果您要转载,请注明出处,并保证文档的完整性。谢谢。

我用的分析工具是UltraEdit32 v10.00,编程工具是C++ Builder 6.0

经过用UltraEdit32分析,发现Access2000和Access2002的数据库加密方式相同,所以以下只针对Access2000的mdb文件。还有就是我用的是16进制的数表示,所以前面加了0x,如果你用的是VB或其他,要注意数值哦。

首先用AccessXP创建了一个空密码的数据库文件db1.mdb,包含一个表,其中有一个字段,没有填任何数据。保存退出然后复制一份为db2.mdb,以独占方式打开2.mdb,并加上密码1324567890123 保存退出。

用UltraEdit32打开这两个数据库,并进行比较。我比较的方法也很简单。在UltraEdit32中,快速的来回点击被打开文件的选项卡(就是在两个文件间来回切换,呵呵。笨办法吧),发现从文件头开始0x42字节处发生变化。

db1.mdb
00000040h:BC 4E BE 68 EC 37 65 D7 9C FA FE CD 28 E6 2B 25 ;
00000050h:8A 60 6C 07 7B 36 CD E1 DF B1 4F 67 13 43 F7 3C ;

00000060h:B1 33 0C F2 79 5B AA 26 7C 2A 4F E9 7C 99 05 13 ;
db2.mdb
00000040h:BC 4E 8F 68 DE 37 56 D7 A8 FA CB CD 1E E6 1C 25 ;
00000050h:B2 60 55 07 4B 36 FC E1 ED B1 7C 67 13 43 F7 3C ;

00000060h:B1 33 0C F2 79 5B AA 26 7C 2A 4F E9 7C 99 05 13 ;

为了看的清楚些,我把不同的字节加了颜色。看出门道了吧,Access97以后的版本里,密码字节不再是连续存放,而是隔一个字节存一个。并且经过加密。到于解密的方法嘛,还是用老办法“异或”!0xBE ^ 0x8F = 0x31,这正好是Ascii码"1"哦。下一个0xEC ^ 0xDE = 0x32 正好是Ascii码"2",呵呵。一直到最后一个不同的0x4F ^ 0x7C =0x33,将取得的字符合成字符串,便是密码明文“1234567890123",千万不要以为这样就收工了。因为这一次是正好碰对了。呵呵。我刚开始也以为就这么简单,于是用CB做了个小程序,试着解了几个mdb密码都还行,可是试到动网论坛的mdb文件时发现取出来的密码不对,晕了。于是用另外一个取mdb密码的工具看了一下,发现人家的就可以正确的取出密码,是Access2000的格式,于是感觉微软加密的方式还是没研究完。继续工作,用UltraEdit32打开动网论坛的数据库dvbbs.mdb,和我前面的加过密的数据库做比较,发现不同的地方很多。只好一个字节一个字节的试。。。。nnn次以后发现第0x62处的这个字节起着关键作用,暂称之为加密标志。

db1.mdb //空密码
00000040h:BC 4E BE 68 EC 37 65 D7 9C FA FE CD 28 E6 2B 25 ;
00000050h:8A 60 6C 07 7B 36 CD E1 DF B1 4F 67 13 43 F7 3C ;

00000060h:B1 33 0C F2 79 5B AA 26 7C 2A 4F E9 7C 99 05 13 ;

db2.mdb //密码为:1234567890123
00000040h:BC 4E 8F 68 DE 37 56 D7 A8 FA CB CD 1E E6 1C 25 ;
00000050h:B2 60 55 07 4B 36 FC E1 ED B1 7C 67 13 43 F7 3C ;

00000060h:B1 33 0C F2 79 5B AA 26 7C 2A 4F E9 7C 99 05 13 ;

dvbbs.mdb //密码为:yemeng.net

00000040h:BC 4E DB 6A 89 37 14 D5 F9 FA 8C CF 4F E6 19 27 ;

00000050h:E4 60 15 05 0F 36 D1 E3 DF B1 53 65 13 43 EB 3E ;

00000060h:B1 33 10 F0 79 5B B6 24 7C 2A 4A E0 7C 99 05 13 ;

 

怎么试呢,还是异或。取0x42处开始的字节0xDB与空密码文件的0x42处字节异或,取0x62处的加密标志与空密码文件0x62处字节异或,然后再把取得的两个值相异或:

(0xDB^0xBE)^(0x10^0x0C)=0x79 嘿嘿。这个值是Ascii的"y",然后取下一个字节(记得隔一个字节取一个)

(0x89^0xEC)^(0x10^0x0C)=0x79 咦,本来这个字节应该是"e"的,怎么变成"y"了?试着不与后面的两个异或值相异或,只计算0x89^0xEC=0x65 得到"e",哈。这下对了。下一个

(0x14^0x65)^(0x10^0C)=0x6D 得到"m",下一个

(0xF9^9C)=0x65 得到"e",注意这里只是这两个数异或。后面的大家可以自己试。

 

这样就总结出规律来了。

解密时,先取出加密文件从文件头开始0x62处的字节,与空密码数据库文件第0x62处相异或,得到一个加密标志。

再从0x42处开始每隔一个字节取一个字节,取得13个加密后的密码字节,分别与空密码数据库文件0x42处每隔一个字节取得的13个字节想异或,得到13个密码半成品。为什么说是半成品呢,因为还要将13个字节的密码每隔一个字节,就与加密标志相异或,最后得到的13个字节才是真正的密码。当然,如果中间有0x0的字节,则说明密码位数不够13位。直接show出来就可以了。

另外我发现加密标志会随着时间或机器不同而不同,所以也没有万能的,不过有一个参照的就可以了。以下代码是我在写这个程序的时候取得的数,和我写这篇文章不是一个时间,所以数值不一样,但最终解密的结果是一样的。大家可以参考一下。

 

对了,还有个重要的就是先得判断数据库的版本,我用了个简单的办法,取0x14处的字节,如果为0就判断为是Access97,如果为1就认为是Access2000或2002的。只是目前没有研究出判断2000和2002的办法,如果哪位知道的话,请指点。

 

代码:

//这里定义的是13个字节作为Access2000异或的源码。与之相对应的加密标志是0x13,ccrun特此注明

//当然你可以用这一组:BE EC 65 9C FE 28 2B 8A 6C 7B CD DF 4F 与这一组相对应的加密标志是0x0c

//呵呵.程序有些乱,希望大家能看的懂。
char PassSource2k[13]={0xa1,0xec,0x7a,0x9c,0xe1,0x28,0x34,0x8a,0x73,0x7b,0xd2,0xdf,0x50};

//Access97的异或源码
char PassSource97[13]={0x86,0xfb,0xec,0x37,0x5d,0x44,0x9c,0xfa,0xc6,0x5e,0x28,0xe6,0x13};

 

void __fastcall TMainForm::GetMdbPass()
{
char PassStrTemp[26],Ver,EncrypFlag,t1;
int FileHandle;
String MdbPassword,MdbVersion,MdbFileName;

FileHandle=FileOpen(MdbFileName,fmOpenRead);
if(FileHandle<0)
{
ShowMessage("文件打开错误!");
return;
}

//取得数据库版本
FileSeek(FileHandle,0x14,0);
FileRead(FileHandle,&Ver,1);

//取得加密标志
FileSeek(FileHandle,0x62,0);
FileRead(FileHandle,&EncrypFlag,1);

//读取加密后的密码到缓冲区
FileSeek(FileHandle,0x42,0);
FileRead(FileHandle,&PassStrTemp,26);
FileClose(FileHandle);

if(Ver<1)
{
MdbVersion="Access 97";
if(int(PassStrTemp[0]^PassSource97[0])==0)
MdbPassword="密码为空!";
else
{
MdbPassword="";
for(int j=0;j<13;j++)
MdbPassword=MdbPassword+char(PassStrTemp[j]^PassSource97[j]);
}
}
else
{
MdbVersion="Access 2000 or 2002";
MdbPassword="";
for(int j=0;j<13;j++)
{
if(j%2==0)

t1=char(0x13^EncrypFlag^PassStrTemp[j*2]^PassSource2k[j]);

//每隔一个字节就与加密标志相异或。这里的加密标志为0x13

else

t1=char(PassStrTemp[j*2]^PassSource2k[j]);
MdbPassword=MdbPassword+t1;
}
}
if(MdbPassword[1]<0x20||MdbPassword[1]>0x7e)
MdbPassword="密码为空!";
EditMdbFileName->Text=MdbFileName;
EditMdbPassword->Text=MdbPassword;
EditMdbVersion->Text=MdbVersion;
}

(还有直接写好的程序当,方便方便)

III.
VC编译ADO的开发环境和客户环境的细微差别可能导致的失败

来源不详

使用属性的Get/Put/PutRef
在VB中,属性的名称并未被检验,无论它是被读取、被赋值,或者赋予一个引用。
Public Sub GetPutPutRef
Dim rs As New ADODB.Recordset
Dim cn As New ADODB.Connection
Dim sz as Integer
cn.Open "Provider=sqloledb;Data Source=yourserver;" & _
"Initial Catalog=pubs;User Id=sa;Password=;"
rs.PageSize = 10
sz = rs.PageSize
rs.ActiveConnection = cn
rs.Open "authors",,adOpenStatic
' ...
rs.Close
cn.Close
End Sub

以下是VC++关于Get/Put/PutRefProperty的演示
1.这个例子演示了省略字符串参数的两种形式:一种是采用常量strMissing,另一种则是由编译器自动生成一个临时的存在于Open方法使用期间的_bstr_t。
2.因为操作数已经是(IDispatch *)的指针,所以没有必要将rs->PutRefActiveConnection(cn)的操作数再进行类型转换。
#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF", "EndOfFile")
#include

void main(void)
{
CoInitialize(NULL);
try
{
_ConnectionPtr cn("ADODB.Connection");
_RecordsetPtr rs("ADODB.Recordset");
_bstr_t strMissing(L"");
long oldPgSz = 0,
newPgSz = 5;

// Note 1
cn->Open("Provider=sqloledb;Data Source=a-tima10;"
"Initial Catalog=pubs;User Id=sa;Password=;",
strMissing, "",
adConnectUnspecified);

oldPgSz = rs->GetPageSize();
// -or-
oldPgSz = rs->PageSize;

rs->PutPageSize(newPgSz);
// -or-
rs->PageSize = newPgSz;

// Note 2
rs->PutRefActiveConnection( cn );
rs->Open("authors", vtMissing, adOpenStatic, adLockReadOnly,
adCmdTable);
printf("Original pagesize = %d, new pagesize = %d\n", oldPgSz,
rs->GetPageSize());
rs->Close();
cn->Close();
}
catch (_com_error &e)
{
printf("Description = %s\n", (char*) e.Description());
}
::CoUninitialize();
}

使用GetItem(x)和Item[x]
下面是VB中关于Item()的标准与交互语法的演示。
Public Sub GetItemItem
Dim rs As New ADODB.Recordset
Dim name as String
rs = rs.Open "authors", "DSN=pubs;", adOpenDynamic, _
adLockBatchOptimistic, adTable
name = rs(0)
' -or-
name = rs.Fields.Item(0)
rs(0) = "Test"
rs.UpdateBatch
' Restore name
rs(0) = name
rs.UpdateBatch
rs.Close
End Sub

以下则是VC++关于Item的演示
当访问collection中的Item时,索引值2必须被转换为long类型以确保正确的构造函数被调用。
#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF", "EndOfFile")
#include

void main(void)
{
CoInitialize(NULL);
try {
_RecordsetPtr rs("ADODB.Recordset");
_variant_t vtFirstName;

rs->Open("authors",
"Provider=sqloledb;Data Source=a-tima10;"
"Initial Catalog=pubs;User Id=sa;Password=;",
adOpenStatic, adLockOptimistic, adCmdTable);
rs->MoveFirst();

// Note 1.取得一个字段的名称
vtFirstName = rs->Fields->GetItem((long)2)->GetValue();
// -or-
vtFirstName = rs->Fields->Item[(long)2]->Value;

printf( "First name = '%s'\n", (char*) ((_bstr_t) vtFirstName));

rs->Fields->GetItem((long)2)->Value = L"TEST";
rs->Update(vtMissing, vtMissing);

// 恢复原名称
rs->Fields->GetItem((long)2)->PutValue(vtFirstName);
// -or-
rs->Fields->GetItem((long)2)->Value = vtFirstName;
rs->Update(vtMissing, vtMissing);
rs->Close();
}
catch (_com_error &e)
{
printf("Description = '%s'\n", (char*) e.Description());
}
::CoUninitialize();
}

利用(IDispatch *)转换ADO对象的指针类型
1.在一个Variant中显式地封装一个活动的Connection对象,然后用(IDispatch *)进行类型转换确保正确的构造函数被调用。同时明确地设置第二个参数为缺省的true,使该对象的引用计数在Recordset::Open操作完成后仍得到正确的维护。
2.表达式(_bstr_t)不是一个类型转换,而是一个_variant_t的操作符,用以从中提取一个_bstr_t字符串。
表达式(char*)也不是一个类型转换,而是一个_bstr_t的操作符,用以从中提取封装在_bstr_t中的字符串的指针。
下面这些代码演示了_variant_t和_bstr_t的一些常见操作。
#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF", "EndOfFile")

#include

void main(void)
{
CoInitialize(NULL);
try
{
_ConnectionPtr pConn("ADODB.Connection");
_RecordsetPtr pRst("ADODB.Recordset");

pConn->Open("Provider=sqloledb;Data Source=a-tima10;"
"Initial Catalog=pubs;User Id=sa;Password=;",
"", "", adConnectUnspecified);
// Note 1
pRst->Open(
"authors",
_variant_t((IDispatch *) pConn, true),
adOpenStatic,
adLockReadOnly,
adCmdTable);
pRst->MoveLast();
// Note 2
printf("Last name is '%s %s'\n",
(char*) ((_bstr_t) pRst->GetFields()->GetItem("au_fname")->GetValue()),
(char*) ((_bstr_t) pRst->Fields->Item["au_lname"]->Value));

pRst->Close();
pConn->Close();
}
catch (_com_error &e)
{
printf("Description = '%s'\n", (char*) e.Description());
}
::CoUninitialize();
}
///////////////////////////////////////////////
VC++对ADO的扩展
///////////////////////////////////////////////
对于VC++程序员而言,每次都要将ADO返回的数据转换成一般的C++数据类型,接着将数据存入一个类或结构总是一件枯燥的事。更讨厌的是这也带来了效率的低下。
因此,ADO提供了一个接口以支持将数据直接返回为一个本地化的C/C++数据类型而非VARIANT,并提供了一系列的预处理宏来方便使用这些接口。这样做的结果是一个复杂的工具可以很轻松的被使用并能获得很好的性能。
一个普通的C/C++客户场景是将一个Recordset中的一条记录绑定到一个包含本地C/C++数据类型的C/C++结构或类之上。如果通过Variant传递数据,这意味着要编写大量的转换代码,以在VARIANT和C/C++本地类型间进行数据转换。VC++对ADO的扩展出现的目的就是要简化这一过程。

如何使用VC++对ADO的扩展

IADORecordBinding接口
VC++对ADO的扩展联系或绑定了一个Recordset对象的各个字段到C/C++变量。当被绑定的Recordset的当前行改变时,其中所有被绑定的字段的值也同样会被拷贝到相应的C/C++变量中。如果需要,被拷贝的数据还会自动进行相应的数据类型转换。
IADORecordBinding接口的BindToRecordset方法将字段绑定到C/C++变量之上。AddNew方法则是增加一个新的行到被绑定的Recordset。Update方法利用C/C++变量的值填充Recordset中新的行或更新已存在的行。
IADORecordBinding接口由Recordset对象实现,你不需要自己编码进行实现。

绑定条目
VC++对ADO的扩展在一个Recordset对象与一个C/C++变量间进行映像(Map)。一个字段与对应的一个变量间的映像被称作一个绑定条目。预定义的宏为数字、定长或不定长数据提供了绑定条目。所有的绑定条目与相应的C/C++变量都被封装、声明在一个从VC++扩展类CADORecordBinding派生的类中。这个CADORecordBinding类在内部由绑定条目宏定义。
在ADO内部,将所有宏的参数都映射在一个OLE DB DBBINDING结构中,并创建一个OLE DB访问子(Accessor)对象来管理所有的行为和字段与变量间的数据转换。OLE DB定义的数据由以下三部分组成:存储数据的缓冲区;一个状态值表示一个字段是否被成功地被存入缓冲区,或变量值是否被成功地存入字段;数据长度。(参见OLE DB程序员参考第6章:读写数据的更多信息)

所需的头文件
为了使用VC++对ADO的扩展,你得在你的应用中包含这个头文件:#include

绑定Recordset的字段
要绑定Recordset的字段到C/C++变量,需要这样做:
1.创建一个CADORecordsetBinding的派生类。
2.在派生类中定义绑定条目和相应的C/C++变量。注意不要使用逗号、分号切断宏。每个宏都会自动地定义适当的分隔符。
为每个被映像的字段定义一个绑定条目。并注意根据不同情况选用ADO_FIXED_LENGTH_ENTRY、 ADO_NUMERIC_ENTRY、ADO_VARIABLE_LENGTH_ENTRY中的某个宏。
3.在你的应用中,创建一个该派生类的实例。从Recordset中获得IADORecordBinding接口,然后调用BindToRecordset方法将Recordset的所有字段绑定到对应的C/C++变量之上。
请参见示例程序以获得更多信息。

接口方法
IADORecordBinding接口只有三个方法:BindToRecordset, AddNew,和Update。每个方法所需的唯一的参数就是一个CADORecordBinding派生类的实例指针。因此,AddNew和Update方法不能使用任何与它们同名的ADO方法中的参数。

语法
BindToRecordset方法将字段绑定到C/C++变量之上。
BindToRecordset(CADORecordBinding *binding)
AddNew方法则引用了它的同名ADO函数,来增加一个新的记录行。
AddNew(CADORecordBinding *binding)
Update方法也引用了它的同名ADO函数,来更新Recordset。
Update(CADORecordBinding *binding)

绑定条目宏
绑定条目宏定义了一个Recordset字段与一个变量间的对应关系。每个条目的绑定宏由开始宏与结束宏组成并配对使用。
定长数据的宏适用于adDate,adBoolean等,数字的宏适用于adTinyInt, adInteger和adDouble等,变长数据的宏适用于adChar, adVarChar和adVarBinary等。所有的数字类型,除了adVarNumeric以外也是定长数据类型。每个宏的族之间都有不同的参数组,因此你可以排除不感兴趣的绑定信息。
参见OLE DB程序员参考附录A:数据类型的更多信息

开始绑定条目
BEGIN_ADO_BINDING(Class)

定长数据:
ADO_FIXED_LENGTH_ENTRY(Ordinal, DataType, Buffer, Status, Modify)
ADO_FIXED_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Modify)
数字型数据:
ADO_NUMERIC_ENTRY(Ordinal, DataType, Buffer, Precision, Scale, Status, Modify)
ADO_NUMERIC_ENTRY2(Ordinal, DataType, Buffer, Precision, Scale, Modify)
变长数据:
ADO_VARIABLE_LENGTH_ENTRY(Ordinal, DataType, Buffer, Size, Status, Length, Modify)
ADO_VARIABLE_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Size, Status, Modify)
ADO_VARIABLE_LENGTH_ENTRY3(Ordinal, DataType, Buffer, Size, Length, Modify)
ADO_VARIABLE_LENGTH_ENTRY4(Ordinal, DataType, Buffer, Size, Modify)

结束绑定
END_ADO_BINDING()
参数 描述
Class 派生类的名字。
Ordinal 从1开始的序号,对应于Recordset中的字段。
DataType 与C/C++变量对应的ADO数据类型(参见DataTypeEnum以获得有效数据类型的列表)。如果需要,字段的值会被转换成该类型的值。
Buffer 对应的C/C++变量的名字。
Size 该C/C++变量的最大字节数。如果是个变长字符串,使用0表示即可。
Status 指示变量的名字。该变量用以表示缓冲是否有效,数据转换是否成功。
值adFldOK意味着转换成功;adFldNull意味着该字段的值为空。其他可能的值见后面的状态值列表。
Modify 逻辑标志。TRUE意味着ADO允许利用变量值更新Recordset中的字段的值。
设置该值为TRUE将允许更新,如果你只想检查字段的值而不想改变它那么就设置为FALSE。
Precision 数字型变量的位数。
Scale 数字型变量的小数位数。
Length 一个4字节变量的名字。该变量将包含缓冲区中数据的实际长度。

状态值
变量Status的值指示了一个字段的值是否被成功的拷贝到了对应的变量中。写数据时,可以给Status赋值为adFldNull来指示该字段将被设置为null。
常量 值 描述
adFldOK 0 一个非空的字段值被返回。
adFldBadAccessor 1 绑定无效。
adFldCantConvertValue 2 值因为符号不匹配或超界外的原因导致无法被正确转换。
adFldNull 3 读字段值时,指示一个空值被返回。写字段值时,指示当字段自身无法编码NULL时该字段将被设置为NULL。
adFldTruncated 4 变长数据或数字被截断。
adFldSignMismatch 5 值是有符号数,而数据类型是无符号数。
adFldDataOverFlow 6 数据值超出界限。
adFldCantCreate 7 不知名的列类型和字段已经被打开。
adFldUnavailable 8 字段值无法确定。比如一个新的未赋值的无缺省值的字段。
adFldPermissionDenied 9 未被允许更新数据。
adFldIntegrityViolation 10 更新字段时值违反了列的完整性要求。
adFldSchemaViolation 11 更新字段时值违反了列的规范要求。
adFldBadStatus 12 更新字段时,无效的状态参数。
adFldDefault 13 更新字段时,使用缺省值。

使用VC++对ADO的扩展的示例
在这个例子中,还使用了COM专有的“智能指针”功能,它能自动处理IADORecordBinding接口的QueryInterface和引用计数。如果没有智能指针,你得这样编码:
IADORecordBinding *picRs = NULL;
...
TESTHR(pRs->QueryInterface(
__uuidof(IADORecordBinding), (LPVOID*)&picRs));
...
if (picRs) picRs->Release();
使用智能指针,你可以用这样的语句从IADORecordBinding接口派生IADORecordBindingPtr类型:
_COM_SMARTPTR_TYPEDEF(IADORecordBinding, __uuidof(IADORecordBinding));
然后这样实例化指针:
IADORecordBindingPtr picRs(pRs);
因为VC++的扩展由Recordset对象实现,因此智能指针picRs的构造函数使用了_RecordsetPtr类指针pRs。构造函数利用pRs调用QueryInterface来获得IADORecordBinding接口。

// 以下即是示例程序
#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF", "EndOfFile")

#include
#include
_COM_SMARTPTR_TYPEDEF(IADORecordBinding, __uuidof(IADORecordBinding));

inline void TESTHR(HRESULT _hr) { if FAILED(_hr) _com_issue_error(_hr); }

class CCustomRs : public CADORecordBinding
{
BEGIN_ADO_BINDING(CCustomRs)
ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_ch_fname,
sizeof(m_ch_fname), m_ul_fnameStatus, false)
ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_ch_lname,
sizeof(m_ch_lname), m_ul_lnameStatus, false)
END_ADO_BINDING()
public:
CHAR m_ch_fname[22];
CHAR m_ch_lname[32];
ULONG m_ul_fnameStatus;
ULONG m_ul_lnameStatus;
};

void main(void)
{
::CoInitialize(NULL);
try
{
_RecordsetPtr pRs("ADODB.Recordset");
CCustomRs rs;
IADORecordBindingPtr picRs(pRs);

pRs->Open("SELECT * FROM Employee ORDER BY lname",
"dsn=pubs;uid=sa;pwd=;",
adOpenStatic, adLockOptimistic, adCmdText);

TESTHR(picRs->BindToRecordset(&rs));

while (!pRs->EndOfFile)
{
// 处理CCustomRs中的数据
printf("Name = %s %s\n",
(rs.m_ul_fnameStatus == adFldOK ? rs.m_ch_fname: ""),
(rs.m_ul_lnameStatus == adFldOK ? rs.m_ch_lname: ""));

// 移动到下一行,新行的值会被自动填充到对应的CCustomRs的变量中
pRs->MoveNext();
}
}
catch (_com_error &e )
{
printf("Error:\n");
printf("Code = %08lx\n", e.Error());
printf("Meaning = %s\n", e.ErrorMessage());
printf("Source = %s\n", (LPCSTR) e.Source());
printf("Description = %s\n", (LPCSTR) e.Description());
}
::CoUninitialize();
}

IV.
Programming with ADO in Visual C++

来源:MSDN
翻译:Abbey

《ADO API参考》用VB的语法描述了ADO API的内容。但ADO程序员却使用着不同的编程语言,比如VB,VC++,VJ++。对此《ADO for VC++的语法索引》提供了符合VC++语法规范的详细描述,包括功能、参数、异常处理等等。
ADO基于若干的COM借口实现,因此它的使用对于一个正进行COM编程的程序员而言更简单。比如,几乎所有使用COM的细节对于VB程序员而言都是隐藏了的,但对于VC++程序员而言却要特别注意。以下是对于C和C++程序员使用ADO和#import指示符方面的概述,主要描述了COM使用的数据类型(Variant, BSTR, and SafeArray)和异常的处理(_com_error)。

使用#import编译指示符
#import编译指示符使使用ADO的方法与属性简单化。这个指示符需要一个类型库文件名,比如ADO.dll(Msado15.dll),并生成对应的头文件,其中包括定义的类型、接口的智能化指针、常量。并且所有的接口都被封装成类。
对于类中的每个操作(或称方法、属性调用),都有一个声明以保证能直接调用它(或称作操作的源形式),以及另一个声明来调用这个源操作并在操作失败时抛出一个COM错误。如果操作是一个属性,那么编译指示符可以为该操作创建一个可交互的类似VB的语法形式。
返回/设置属性的操作有对应的形式化的名字—GetProperty/PutPropert,而设置一个指向某个ADO对象的指针型属性值时则是PutRefProperty。你将使用如下的形式读写属性的值:
variable = objectPtr->GetProperty(); // 读取属性的值
objectPtr->PutProperty(value); // 设置属性的值
objectPtr->PutRefProperty(&value); // 设置一个指针型的属性的值

直接使用属性
__declspec(property...)编译指示符是微软定义的一个针对C语言的扩展,使一个函数象一个属性那样被使用。这样你就可以采用如下的语法形式象在使用VB一样读写一个属性的值: objectPtr->property = value; // 设置属性的值
variable = objectPtr->property; // 读取属性的值
__declspec(property...)编译指示符只能针对属性的读写函数使用,并根据属性是否可供读写自动生成对应的调用形式。每个属性可能有GetProperty, PutProperty,PutRefProperty三个函数,但这个编译符只能生成其中的两种交互形式。比如,Command对象的ActiveConnection属性有GetActiveConnection和PutRefActiveConnection这两个读写函数。而PutRef-的形式在实践中是个好的选择,你可以将一个活动的Connection对象的指针保存在这个属性中。另一方面,Recordset对象则有Get-, Put-, and PutRefActiveConnection操作,但却没有可交互的语法形式。

Collections,GetItem方法和Item属性
ADO定义了几种集合Collection,包括Fields,Parameters,Properties,和Errors。在Visual C++中,GetItem(index)方法返回Collection中的某个成员。Index是一个Variant型的参数,内容可以是一个该成员对应的序数,也可以是一个包括其名称的字符串。
__declspec(property...)编译指示符为Item属性生成对应于GetItem()方法的直接使用形式(上文提到的可交互的语法形式)。这种形式类似于引用数组元素时使用[]的语法形式:
collectionPtr->GetItem(index);
collectionPtr->Item[index];
举例说明,要给一个Recordset对象rs中的某个字段赋值,而这个Recordset对象派生于pubs数据库中的authors表。使用Item()属性访问这个Recordset的Fields集合中的第三个字段(集合总是从0开始编号,假设第三个字段名为au_fname)。然后调用Value()方法为该字段赋一个字符串值。
Visual Basic的语法形式:
rs.Fields.Item(2).Value = "value"
rs.Fields.Item("au_fname").Value = "value"
或者:
rs(2) = "value"
rs!au_fname = "value"
Visual C++的语法形式:
rs->Fields->GetItem(2)->PutValue("value");
rs->Fields->GetItem("au_fname")->PutValue("value");
或者:
rs->Fields->Item[2]->Value = "value";
rs->Fields->Item["au_fname"]->Value = "value";

COM特定的数据类型
一般的,你在《ADO API Reference》中看到的VB的数据类型在VC++中也能找到对应的类型。其中包括标准的数据类型,比如unsigned char对应VB的Byte,short对应Integer,long对应Long。参见《Syntax Indexes》将可以获得关于所需操作数的更详细内容。
而作为例外的专属于COM使用的数据类型则有:Variant, BSTR, and SafeArray.

Variant
Variant是一个结构化的数据类型,包含了一个成员值及其数据类型的表示。Variant可以表示相当多的数据类型,甚至另一个Variant, BSTR, Boolean, Idispatch或Iunknown指针,货币,日期等等。同时COM也提供了许多方法使数据类型间的转换更简单化。
_variant_t类封装并管理Variant这一数据类型。
当《ADO API Reference》中说到一个方法或属性要使用一个参数时,通常意味着需要一个_variant_t类型的参数。这条准则在《ADO API Reference》的Parameters一章中得到了明白无误的表述。作为例外的是,有时则会要求操作数是一个标准的数据类型,比如Long或Byte, 或者一个枚举值。另一个例外是要求操作数是一个字符串String。

BSTR
BSTR (Basic STRing)也是一个结构化的数据类型,包括了串及串的长度。COM提供了方法进行串的空间分配、操作、释放。
_bstr_t类封装并管理BSTR这一数据类型。
当《ADO API Reference》中说到一个方法或属性要使用一个字符串参数时,通常意味着需要一个类_bstr_t型的参数。

_variant_t和_bstr_t类的强制类型转换
通常当传递一个_variant_t或_bstr_t参数给一个操作时并不需要显式的类型转换代码。如果_variant_t或_bstr_t类提供了对应于该参数类型的构造函数,那么编译器将会自动生成适当的_variant_t或_bstr_t值。
然而,当参数模棱两可时,即对应了多个构造函数时,你就必须显式地调用正确的构造函数以获得正确的参数。比如,Recordset::Open方法的函数声明如下:
HRESULT Open (
const _variant_t & Source,
const _variant_t & ActiveConnection,
enum CursorTypeEnum CursorType,
enum LockTypeEnum LockType,
long Options );
其中参数ActiveConnection就是针对一个variant_t型变量的引用,它可以是一个连接串或者一个指向已打开的Connection对象的指针。
正确的_variant_t型参数会被构造,无论你传递的是一个类似"DSN=pubs;uid=sa;pwd=;"这样的字符串,或者是一个类似"(IDispatch *) pConn"的指针。
或者你还可以显式的编写"_variant_t((IDispatch *) pConn, true)"这样的代码来传递一个包含指针的_variant_t变量。这里的强制类型转换(IDispatch *)避免了可能调用IUnknown接口构造函数的模棱两可性。
虽然很少提及但特别重要的是,ADO总是一个IDispatch接口。任何被传递的被包含在Variant中的指针都必须被转换为一个IDispatch接口指针。
最后需要说明的是构造函数的第二个逻辑参数是可选择的,它的缺省值是True。这个参数将决定Variant的构造函数是否调用内嵌的AddRef()方法,并在完成ADO的方法或属性调用后是否自动调用_variant_t::Release()方法

SafeArray
SafeArray也是一种结构化的数据类型,包含了一个由其它数据类型的数据元素组成的数组。之所以称之为安全的数组是因为它包含了每一维的边界信息,并限制在边界内进行数组元素的访问。
当《ADO API Reference》中说到一个方法或属性要使用或者返回一个数组时,通常意味着是一个SafeArray数组,而非一个本地化的C/C++数组。
比如,Connection对象的OpenSchema方法的第二个参数需要一个由Variant值组成的数组。这些Variant值必须作为一个SafeArray数组的元素进行传递。而这个SafeArray数组本身又被作为一个Variant进行传递。
更进一步的,Find方法的第一个参数是一个指向一维SafeArray数组的Variant;AddNew方法的可选的第一与第二个参数也是一个一维的SafeArray数组;GetRows方法的返回值则是一个包含二维SafeArray数组的Variant。

缺省参数
VB允许省略方法的某些参数。例如,Recordset对象的Open方法有五个参数,但是你可以跳过中间的参数并省略之后的参数。被省略的参数会被自动创建的BSTR或Variant缺省值替代。
在C/C++中,所有的操作数必须被明确。如果你想定义一个字符串型的缺省参数,那么就定义一个包含空字符串的_bstr_t。如果想定义一个Variant型的缺省参数,那么就定义一个值为DISP_E_PARAMNOTFOUND、类型为VT_ERROR的_variant_t。你还可以使用#import编译指示符提供的与之等价的常量vtMissing。
vtMissing的使用有三种意外情形:Connection与Command对象的Execute方法,Recordset对象的NextRecordset方法。
_RecordsetPtr Execute( _bstr_t CommandText, VARIANT * RecordsAffected,
long Options ); // Connection
_RecordsetPtr Execute( VARIANT * RecordsAffected, VARIANT * Parameters,
long Options ); // Command
_RecordsetPtr NextRecordset( VARIANT * RecordsAffected ); // Recordset
参数RecordsAffected与Parameters都是指向Variant的指针。Parameters是一个传入参数,指向一个包含一个或一组参数信息的Variant的地址,将决定命令执行的内容。RecordsAffected是一个传出参数,指向一个包含该方法返回时影响行的数目的Variant的地址。
在Command对象的Execute方法中,如果只是没有参数的话,需要将Parameters设置为&vtMissing (推荐使用)或者一个空指针(NULL)。如果传递的是一个空指针,那么等价的vtMissing会被传递并完成操作。
在所有的方法中,通过设置RecordsAffected为空指针可以指示不需返回被影响的记录的数目。此时,这个空指针实际成为了指示该方法抛弃被影响记录数目的指示器。
因此,如下的编码是有效的:
pConnection->Execute("commandText", NULL, adCmdText);
pCommand->Execute(NULL, NULL, adCmdText);
pRecordset->NextRecordset(NULL);

错误的处理
在COM中,大多数的操作总是返回一个HRESULT值说明该函数是否被成功完成。编译指示符#import为所有源方法和属性提供了封装好的代码并检查返回的HRESULT值。如果HRESULT指示失败,这些封装代码将会通过调用以HRESULT为参数的_com_issue_errorex()抛出一个COM错误。COM错误对象将在try-catch块中被捕获(出于效率的考虑,实际捕获的是一个_com_error对象的引用指针)。
记住,由ADO操作失败产生的错误才是ADO错误。由下层提供者返回的错误以Connection对象中Errors集合中的一个Error对象的形式出现。
编译指示符#import只能为在ADO.dll中声明的方法和属性提供错误处理例程。因此,你可以基于同样的错误处理机制编写自己的错误检查宏或内置函数。参见《Visual C++扩展》以及本文后续的示例代码。

在VC++与VB中编码时的约定
下面是ADO文档中关于如何使用VB和VC++编写代码的一个概览。

声明一个ADO对象
在VB中,一个ADO对象变量(此处以Recordset对象为例)如下声明:
Dim rst As ADODB.Recordset
子句"ADODB.Recordset"是在注册表中登记的Recordset对象的ProgID。而一个Record对象的实例如下声明: Dim rst As New ADODB.Recordset
或者:
Dim rst As ADODB.Recordset
Set rst = New ADODB.Recordset
而在VC++中,#import为所有的ADO对象生成了智能的指针类型。比如一个指向_Recordset对象的指针变量的数据类型为_RecordsetPtr,并如下声明:
_RecordsetPtr rs;
而一个_Recordset对象的实例则如下声明:
_RecordsetPtr rs("ADODB.Recordset");
或者:
_RecordsetPtr rs;
rs.CreateInstance("ADODB.Recordset");
或者:
_RecordsetPtr rs;
rs.CreateInstance(__uuidof(_Recordset));
当CreateInstance方法被成功调用后,该变量可被如此使用:rs->Open(...);
注意,如果变量是一个类的实例则用"."操作符,若是一个指向实例的指针则应使用"->"操作符。
一个变量能通过两种方式被使用。因为"->"操作符被重载,允许一个对象实例类似一个接口指针那样被使用;"->"操作符返回该指针;而由这个返回的指针访问_Recordset对象的成员。

编写省略String参数的代码
当你需要利用VB编写省略String参数的代码时,只需简单的略掉该操作数即可。但在VC++中,你必须指定该操作数为一个包含空字符串的_bstr_t变量:_bstr_t strMissing(L"");

编写省略Variant参数的代码
当你需要利用VB编写省略Variant参数的代码时,只需简单的略掉该操作数即可。但在VC++中,你必须指定所有的操作数。编写省略Variant参数的代码只需将该Variant设为专门的值,可以定义一个值为DISP_E_PARAMNOTFOUND、类型为VT_ERROR的_variant_t。还可以使用#import编译指示符提供的与之等价的常量vtMissing。
_variant_t vtMissingYours(DISP_E_PARAMNOTFOUND, VT_ERROR);
或者:
...vtMissing...;

声明一个Variant
在VB中,一个Variant如下被声明:
Dim VariableName As Variant
在VC++中,定义一个_variant_t型的变量即可。主要有以下几种形式。注意:这些声明只是你在变成时刻采用的一个粗略的思路。
_variant_t VariableName(value);
_variant_t VariableName((data type cast) value);
_variant_t VariableName(value, VT_DATATYPE);
_variant_t VariableName(interface * value, bool fAddRef = true);

使用Variants数组
在VB中,利用Dim语句可以进行Variant数组的编程,并可以使用Array的函数。见如下示例:
Public Sub ArrayOfVariants
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim fld As ADODB.Field

cn.Open "DSN=pubs", "sa", ""
rs = cn.OpenSchema(adSchemaColumns, _
Array(Empty, Empty, "authors", Empty))
For Each fld in rs.Fields
Debug.Print "Name = "; fld.Name
Next fld
rs.Close
cn.Close
End Sub
以下的代码演示了如何通过一个_variant_t使用一个SafeArray数组。注意注释对应了编码的步骤。
1.再一次的,TESTHR()内置函数被定义以利用预存的错误处理机制。
2.如果你只需要一个一维数组,你可以使用SafeArrayCreateVector,而非SAFEARRAYBOUND声明与SafeArrayCreate函数。下面的代码使用了SafeArrayCreate:
SAFEARRAYBOUND sabound[1];
sabound[0].lLbound = 0;
sabound[0].cElements = 4;
pSa = SafeArrayCreate(VT_VARIANT, 1, sabound);
3.枚举常量adSchemaColumns定义的模式,决定了与TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME和COLUMN_NAME四列相联系。为此,一个有四个Variant元素的数组被创建。而对应于第三列TABLE_NAME的值被设置。
由若干列组成的返回的Recordset只是对应的所有列的一个子集,并且每一行的值保持了一一对应。
4.熟悉SafeArrays的人也许会对退出前没有调用SafeArrayDestroy()感到惊奇。实际上,在这种情况下调用SafeArrayDestroy()会导致一个运行时的异常发生。这是因为vtCriteria的析构函数会在_variant_t超出使用范围时调用VariantClear(),从而释放SafeArray。只调用SafeArrayDestroy,而没有手动清除_variant_t,将会导致析构函数试图去清除一个无效的SafeArray指针。如果要调用SafeArrayDestroy(),那么代码应该象这样:
TESTHR(SafeArrayDestroy(pSa));
vtCriteria.vt = VT_EMPTY;
vtCriteria.parray = NULL;
实际更像是让_variant_t管理SafeArray。

完整的代码如下:
#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF", "EndOfFile")
#include

// Note 1
inline void TESTHR( HRESULT _hr )
{ if FAILED(_hr) _com_issue_error(_hr); }

void main(void)
{
CoInitialize(NULL);
try
{
_RecordsetPtr pRs("ADODB.Recordset");
_ConnectionPtr pCn("ADODB.Connection");
_variant_t vtTableName("authors"),
vtCriteria;
long ix[1];
SAFEARRAY *pSa = NULL;

pCn->Open("DSN=pubs;User ID=sa;pwd=;Provider=MSDASQL;", "", "",
adConnectUnspecified);
// Note 2, Note 3
pSa = SafeArrayCreateVector(VT_VARIANT, 1, 4);
if (!pSa) _com_issue_error(E_OUTOFMEMORY);

// 为第三个元素赋值TABLE_NAME(索引值2).
ix[0] = 2;
TESTHR(SafeArrayPutElement(pSa, ix, &vtTableName));

// 由于Variant没有SafeArray的构造函数,所以手工设置Variant的数据类型和值。
vtCriteria.vt = VT_ARRAY | VT_VARIANT;
vtCriteria.parray = pSa;

pRs = pCn->OpenSchema(adSchemaColumns, vtCriteria, vtMissing);

long limit = pRs->GetFields()->Count;
for (long x = 0; x < limit; x++)
printf("%d: %s\n", x+1,
((char*) pRs->GetFields()->Item[x]->Name));
// Note 4
pRs->Close();
pCn->Close();
}
catch (_com_error &e)
{
printf("Error:\n");
printf("Code = %08lx\n", e.Error());
printf("Code meaning = %s\n", (char*) e.ErrorMessage());
printf("Source = %s\n", (char*) e.Source());
printf("Description = %s\n", (char*) e.Description());
}
CoUninitialize();
}

使用属性的Get/Put/PutRef
在VB中,属性的名称并未被检验,无论它是被读取、被赋值,或者赋予一个引用。
Public Sub GetPutPutRef
Dim rs As New ADODB.Recordset
Dim cn As New ADODB.Connection
Dim sz as Integer
cn.Open "Provider=sqloledb;Data Source=yourserver;" & _
"Initial Catalog=pubs;User Id=sa;Password=;"
rs.PageSize = 10
sz = rs.PageSize
rs.ActiveConnection = cn
rs.Open "authors",,adOpenStatic
' ...
rs.Close
cn.Close
End Sub

以下是VC++关于Get/Put/PutRefProperty的演示
1.这个例子演示了省略字符串参数的两种形式:一种是采用常量strMissing,另一种则是由编译器自动生成一个临时的存在于Open方法使用期间的_bstr_t。
2.因为操作数已经是(IDispatch *)的指针,所以没有必要将rs->PutRefActiveConnection(cn)的操作数再进行类型转换。
#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF", "EndOfFile")
#include

void main(void)
{
CoInitialize(NULL);
try
{
_ConnectionPtr cn("ADODB.Connection");
_RecordsetPtr rs("ADODB.Recordset");
_bstr_t strMissing(L"");
long oldPgSz = 0,
newPgSz = 5;

// Note 1
cn->Open("Provider=sqloledb;Data Source=a-tima10;"
"Initial Catalog=pubs;User Id=sa;Password=;",
strMissing, "",
adConnectUnspecified);

oldPgSz = rs->GetPageSize();
// -or-
oldPgSz = rs->PageSize;

rs->PutPageSize(newPgSz);
// -or-
rs->PageSize = newPgSz;

// Note 2
rs->PutRefActiveConnection( cn );
rs->Open("authors", vtMissing, adOpenStatic, adLockReadOnly,
adCmdTable);
printf("Original pagesize = %d, new pagesize = %d\n", oldPgSz,
rs->GetPageSize());
rs->Close();
cn->Close();
}
catch (_com_error &e)
{
printf("Description = %s\n", (char*) e.Description());
}
::CoUninitialize();
}

使用GetItem(x)和Item[x]
下面是VB中关于Item()的标准与交互语法的演示。
Public Sub GetItemItem
Dim rs As New ADODB.Recordset
Dim name as String
rs = rs.Open "authors", "DSN=pubs;", adOpenDynamic, _
adLockBatchOptimistic, adTable
name = rs(0)
' -or-
name = rs.Fields.Item(0)
rs(0) = "Test"
rs.UpdateBatch
' Restore name
rs(0) = name
rs.UpdateBatch
rs.Close
End Sub

以下则是VC++关于Item的演示
当访问collection中的Item时,索引值2必须被转换为long类型以确保正确的构造函数被调用。
#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF", "EndOfFile")
#include

void main(void)
{
CoInitialize(NULL);
try {
_RecordsetPtr rs("ADODB.Recordset");
_variant_t vtFirstName;

rs->Open("authors",
"Provider=sqloledb;Data Source=a-tima10;"
"Initial Catalog=pubs;User Id=sa;Password=;",
adOpenStatic, adLockOptimistic, adCmdTable);
rs->MoveFirst();

// Note 1.取得一个字段的名称
vtFirstName = rs->Fields->GetItem((long)2)->GetValue();
// -or-
vtFirstName = rs->Fields->Item[(long)2]->Value;

printf( "First name = '%s'\n", (char*) ((_bstr_t) vtFirstName));

rs->Fields->GetItem((long)2)->Value = L"TEST";
rs->Update(vtMissing, vtMissing);

// 恢复原名称
rs->Fields->GetItem((long)2)->PutValue(vtFirstName);
// -or-
rs->Fields->GetItem((long)2)->Value = vtFirstName;
rs->Update(vtMissing, vtMissing);
rs->Close();
}
catch (_com_error &e)
{
printf("Description = '%s'\n", (char*) e.Description());
}
::CoUninitialize();
}

利用(IDispatch *)转换ADO对象的指针类型
1.在一个Variant中显式地封装一个活动的Connection对象,然后用(IDispatch *)进行类型转换确保正确的构造函数被调用。同时明确地设置第二个参数为缺省的true,使该对象的引用计数在Recordset::Open操作完成后仍得到正确的维护。
2.表达式(_bstr_t)不是一个类型转换,而是一个_variant_t的操作符,用以从中提取一个_bstr_t字符串。
表达式(char*)也不是一个类型转换,而是一个_bstr_t的操作符,用以从中提取封装在_bstr_t中的字符串的指针。
下面这些代码演示了_variant_t和_bstr_t的一些常见操作。
#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF", "EndOfFile")

#include

void main(void)
{
CoInitialize(NULL);
try
{
_ConnectionPtr pConn("ADODB.Connection");
_RecordsetPtr pRst("ADODB.Recordset");

pConn->Open("Provider=sqloledb;Data Source=a-tima10;"
"Initial Catalog=pubs;User Id=sa;Password=;",
"", "", adConnectUnspecified);
// Note 1
pRst->Open(
"authors",
_variant_t((IDispatch *) pConn, true),
adOpenStatic,
adLockReadOnly,
adCmdTable);
pRst->MoveLast();
// Note 2
printf("Last name is '%s %s'\n",
(char*) ((_bstr_t) pRst->GetFields()->GetItem("au_fname")->GetValue()),
(char*) ((_bstr_t) pRst->Fields->Item["au_lname"]->Value));

pRst->Close();
pConn->Close();
}
catch (_com_error &e)
{
printf("Description = '%s'\n", (char*) e.Description());
}
::CoUninitialize();
}
///////////////////////////////////////////////
VC++对ADO的扩展
///////////////////////////////////////////////
对于VC++程序员而言,每次都要将ADO返回的数据转换成一般的C++数据类型,接着将数据存入一个类或结构总是一件枯燥的事。更讨厌的是这也带来了效率的低下。
因此,ADO提供了一个接口以支持将数据直接返回为一个本地化的C/C++数据类型而非VARIANT,并提供了一系列的预处理宏来方便使用这些接口。这样做的结果是一个复杂的工具可以很轻松的被使用并能获得很好的性能。
一个普通的C/C++客户场景是将一个Recordset中的一条记录绑定到一个包含本地C/C++数据类型的C/C++结构或类之上。如果通过Variant传递数据,这意味着要编写大量的转换代码,以在VARIANT和C/C++本地类型间进行数据转换。VC++对ADO的扩展出现的目的就是要简化这一过程。

如何使用VC++对ADO的扩展

IADORecordBinding接口
VC++对ADO的扩展联系或绑定了一个Recordset对象的各个字段到C/C++变量。当被绑定的Recordset的当前行改变时,其中所有被绑定的字段的值也同样会被拷贝到相应的C/C++变量中。如果需要,被拷贝的数据还会自动进行相应的数据类型转换。
IADORecordBinding接口的BindToRecordset方法将字段绑定到C/C++变量之上。AddNew方法则是增加一个新的行到被绑定的Recordset。Update方法利用C/C++变量的值填充Recordset中新的行或更新已存在的行。
IADORecordBinding接口由Recordset对象实现,你不需要自己编码进行实现。

绑定条目
VC++对ADO的扩展在一个Recordset对象与一个C/C++变量间进行映像(Map)。一个字段与对应的一个变量间的映像被称作一个绑定条目。预定义的宏为数字、定长或不定长数据提供了绑定条目。所有的绑定条目与相应的C/C++变量都被封装、声明在一个从VC++扩展类CADORecordBinding派生的类中。这个CADORecordBinding类在内部由绑定条目宏定义。
在ADO内部,将所有宏的参数都映射在一个OLE DB DBBINDING结构中,并创建一个OLE DB访问子(Accessor)对象来管理所有的行为和字段与变量间的数据转换。OLE DB定义的数据由以下三部分组成:存储数据的缓冲区;一个状态值表示一个字段是否被成功地被存入缓冲区,或变量值是否被成功地存入字段;数据长度。(参见OLE DB程序员参考第6章:读写数据的更多信息)

所需的头文件
为了使用VC++对ADO的扩展,你得在你的应用中包含这个头文件:#include

绑定Recordset的字段
要绑定Recordset的字段到C/C++变量,需要这样做:
1.创建一个CADORecordsetBinding的派生类。
2.在派生类中定义绑定条目和相应的C/C++变量。注意不要使用逗号、分号切断宏。每个宏都会自动地定义适当的分隔符。
为每个被映像的字段定义一个绑定条目。并注意根据不同情况选用ADO_FIXED_LENGTH_ENTRY、 ADO_NUMERIC_ENTRY、ADO_VARIABLE_LENGTH_ENTRY中的某个宏。
3.在你的应用中,创建一个该派生类的实例。从Recordset中获得IADORecordBinding接口,然后调用BindToRecordset方法将Recordset的所有字段绑定到对应的C/C++变量之上。
请参见示例程序以获得更多信息。

接口方法
IADORecordBinding接口只有三个方法:BindToRecordset, AddNew,和Update。每个方法所需的唯一的参数就是一个CADORecordBinding派生类的实例指针。因此,AddNew和Update方法不能使用任何与它们同名的ADO方法中的参数。

语法
BindToRecordset方法将字段绑定到C/C++变量之上。
BindToRecordset(CADORecordBinding *binding)
AddNew方法则引用了它的同名ADO函数,来增加一个新的记录行。
AddNew(CADORecordBinding *binding)
Update方法也引用了它的同名ADO函数,来更新Recordset。
Update(CADORecordBinding *binding)

绑定条目宏
绑定条目宏定义了一个Recordset字段与一个变量间的对应关系。每个条目的绑定宏由开始宏与结束宏组成并配对使用。
定长数据的宏适用于adDate,adBoolean等,数字的宏适用于adTinyInt, adInteger和adDouble等,变长数据的宏适用于adChar, adVarChar和adVarBinary等。所有的数字类型,除了adVarNumeric以外也是定长数据类型。每个宏的族之间都有不同的参数组,因此你可以排除不感兴趣的绑定信息。
参见OLE DB程序员参考附录A:数据类型的更多信息

开始绑定条目
BEGIN_ADO_BINDING(Class)

定长数据:
ADO_FIXED_LENGTH_ENTRY(Ordinal, DataType, Buffer, Status, Modify)
ADO_FIXED_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Modify)
数字型数据:
ADO_NUMERIC_ENTRY(Ordinal, DataType, Buffer, Precision, Scale, Status, Modify)
ADO_NUMERIC_ENTRY2(Ordinal, DataType, Buffer, Precision, Scale, Modify)
变长数据:
ADO_VARIABLE_LENGTH_ENTRY(Ordinal, DataType, Buffer, Size, Status, Length, Modify)
ADO_VARIABLE_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Size, Status, Modify)
ADO_VARIABLE_LENGTH_ENTRY3(Ordinal, DataType, Buffer, Size, Length, Modify)
ADO_VARIABLE_LENGTH_ENTRY4(Ordinal, DataType, Buffer, Size, Modify)

结束绑定
END_ADO_BINDING()
参数 描述
Class 派生类的名字。
Ordinal 从1开始的序号,对应于Recordset中的字段。
DataType 与C/C++变量对应的ADO数据类型(参见DataTypeEnum以获得有效数据类型的列表)。如果需要,字段的值会被转换成该类型的值。
Buffer 对应的C/C++变量的名字。
Size 该C/C++变量的最大字节数。如果是个变长字符串,使用0表示即可。
Status 指示变量的名字。该变量用以表示缓冲是否有效,数据转换是否成功。
值adFldOK意味着转换成功;adFldNull意味着该字段的值为空。其他可能的值见后面的状态值列表。
Modify 逻辑标志。TRUE意味着ADO允许利用变量值更新Recordset中的字段的值。
设置该值为TRUE将允许更新,如果你只想检查字段的值而不想改变它那么就设置为FALSE。
Precision 数字型变量的位数。
Scale 数字型变量的小数位数。
Length 一个4字节变量的名字。该变量将包含缓冲区中数据的实际长度。

状态值
变量Status的值指示了一个字段的值是否被成功的拷贝到了对应的变量中。写数据时,可以给Status赋值为adFldNull来指示该字段将被设置为null。
常量 值 描述
adFldOK 0 一个非空的字段值被返回。
adFldBadAccessor 1 绑定无效。
adFldCantConvertValue 2 值因为符号不匹配或超界外的原因导致无法被正确转换。
adFldNull 3 读字段值时,指示一个空值被返回。写字段值时,指示当字段自身无法编码NULL时该字段将被设置为NULL。
adFldTruncated 4 变长数据或数字被截断。
adFldSignMismatch 5 值是有符号数,而数据类型是无符号数。
adFldDataOverFlow 6 数据值超出界限。
adFldCantCreate 7 不知名的列类型和字段已经被打开。
adFldUnavailable 8 字段值无法确定。比如一个新的未赋值的无缺省值的字段。
adFldPermissionDenied 9 未被允许更新数据。
adFldIntegrityViolation 10 更新字段时值违反了列的完整性要求。
adFldSchemaViolation 11 更新字段时值违反了列的规范要求。
adFldBadStatus 12 更新字段时,无效的状态参数。
adFldDefault 13 更新字段时,使用缺省值。

使用VC++对ADO的扩展的示例
在这个例子中,还使用了COM专有的“智能指针”功能,它能自动处理IADORecordBinding接口的QueryInterface和引用计数。如果没有智能指针,你得这样编码:
IADORecordBinding *picRs = NULL;
...
TESTHR(pRs->QueryInterface(
__uuidof(IADORecordBinding), (LPVOID*)&picRs));
...
if (picRs) picRs->Release();
使用智能指针,你可以用这样的语句从IADORecordBinding接口派生IADORecordBindingPtr类型:
_COM_SMARTPTR_TYPEDEF(IADORecordBinding, __uuidof(IADORecordBinding));
然后这样实例化指针:
IADORecordBindingPtr picRs(pRs);
因为VC++的扩展由Recordset对象实现,因此智能指针picRs的构造函数使用了_RecordsetPtr类指针pRs。构造函数利用pRs调用QueryInterface来获得IADORecordBinding接口。

// 以下即是示例程序
#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF", "EndOfFile")

#include
#include
_COM_SMARTPTR_TYPEDEF(IADORecordBinding, __uuidof(IADORecordBinding));

inline void TESTHR(HRESULT _hr) { if FAILED(_hr) _com_issue_error(_hr); }

class CCustomRs : public CADORecordBinding
{
BEGIN_ADO_BINDING(CCustomRs)
ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_ch_fname,
sizeof(m_ch_fname), m_ul_fnameStatus, false)
ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_ch_lname,
sizeof(m_ch_lname), m_ul_lnameStatus, false)
END_ADO_BINDING()
public:
CHAR m_ch_fname[22];
CHAR m_ch_lname[32];
ULONG m_ul_fnameStatus;
ULONG m_ul_lnameStatus;
};

void main(void)
{
::CoInitialize(NULL);
try
{
_RecordsetPtr pRs("ADODB.Recordset");
CCustomRs rs;
IADORecordBindingPtr picRs(pRs);

pRs->Open("SELECT * FROM Employee ORDER BY lname",
"dsn=pubs;uid=sa;pwd=;",
adOpenStatic, adLockOptimistic, adCmdText);

TESTHR(picRs->BindToRecordset(&rs));

while (!pRs->EndOfFile)
{
// 处理CCustomRs中的数据
printf("Name = %s %s\n",
(rs.m_ul_fnameStatus == adFldOK ? rs.m_ch_fname: ""),
(rs.m_ul_lnameStatus == adFldOK ? rs.m_ch_lname: ""));

// 移动到下一行,新行的值会被自动填充到对应的CCustomRs的变量中
pRs->MoveNext();
}
}
catch (_com_error &e )
{
printf("Error:\n");
printf("Code = %08lx\n", e.Error());
printf("Meaning = %s\n", e.ErrorMessage());
printf("Source = %s\n", (LPCSTR) e.Source());
printf("Description = %s\n", (LPCSTR) e.Description());
}
::CoUninitialize();
}

V.
ADO相关的几点

(按:今天看到blog的访问量居然过了千。呵呵,根据趋势来看,主要是因为CCBOY老大的blog上面有我的链接的缘故。其实我这个地方比较乏味,都是些当时想到用到的东西,原创又少。难怪留言寥寥。不过通过blog又认识好几位新朋友,倒是不错的。由于急着写手头要用的程序,QuickGraph的阅读笔记暂时放一放。还是羡慕Jonathan de Halleux时间多多,一周不见,又整出个GUnithttp://www.codeproject.com/useritems/gunit.asp 。哎,佩服佩服呀。还有就是C++真的不吃香啦,Hans Dietrich写的http://www.codeproject.com/useritems/XBitArray.asp 我觉得还不错,可是得分居然连4都没到。没办法,现在不流行这些啦。哎,在各大C/C++站点越逛越觉得没劲啦。好歹我也是从TC2.0,BC3.1,VC1.5一路写到现在。呜呼,早点逃命要紧。)

1.如何通过数据源建立ADO连接字符串对话框
出处http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q225132
http://suppor-t.microsoft.com/default.aspx?scid=KB;EN-US;Q225132

HRESULT hr;
IDataSourceLocatorPtr dlPrompt=NULL;

try
{
// 初始化DataLinks对象
hr=dlPrompt.CreateInstance(__uuidof(DataLinks));
if(FAILED(hr))
throw(_com_error(hr,NULL));

// 建立连接
pConn=dlPrompt->PromptNew();

// 如果 conn 为 NULL
if(pConn==NULL)
return;
}
catch (_com_error &e)
{
AfxMessageBox(e.ErrorMessage());
}

这是已经可以得到连接String为pConn->ConnectionString,方便呀

对了,还要
#import "C:\program files\common files\System\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
//如果使用 ADO 2.0 加入下面代码
#import "C:\Program Files\Common Files\System\Ole DB\msdasc.dll" no_namespace
//如果使用 ADO 2.1 加入下面代码
#import "C:\Program Files\Common Files\System\Ole DB\oledb32.dll" no_namespace

2. CGridCtrl的应用
陈松乐大侠在http://www.vckbase.com/document/viewdoc.asp?id=598 讨论了使用虚模式的CGridCtrl和数据库的连接。虽然原文是讲ODBC,可是可以很容易使用ADO改进。
不过用Chris Maunder的网格控件不能显示图片字段,还得自定义使用弹出窗口绘图

3. 使用ADO可以用如下两个控件:
Microsoft ADO Data Control,version 6.0 (OLEDB)
Microsoft DataGrid Control,version 6.0 (OLEDB)
控件注册方法是:
a) DCOM98(分布式组件对象模型)一般假定已经安装。
b) MDAC(microsoft data access componment)的安装,下载页面
http://msdn.microsoft.com/library/default.asp?url=/downloads/list/dataaccess.asp
c) msadodc.ocx 和Msdatgrd.ocx控件的注册。

4. Carlos Antollini的两个类封装的相当方便,看看后面有多少人支持就让人放心
A set of ADO Classes - version 2.10
http://www.codeproject.com/database/caaadoclass1.asp
A set of ADOX Classes
http://www.codeproject.com/database/caaadoxclass.asp

对了还有文章
ADO Connection Strings
http://www.codeproject.com/database/connectionstrings.asp

5. George Poulose的
Interactive SQL Tool (using ADO)
http://home.att.net/~gpoulose/
可是新版本不开放源代码啦,谁有更好的源代码吗?

6. 手工创建你所需的UDL文件
在你所想创建UDL文件的目录中单击右键,选择从菜单 新建|Microsoft 数据连接,然后将新创建的UDL文件更改为你所希望的文件名(.UDL扩展名不能改变)。
注:如果操作系统是Window 2000,先创建一个文本文件,再将该文本文件的扩展名改为 "udl"。
然后双击所创建的UDL文件,即可视化地完成数据源的设定。
使用UDL文件必须在系统中先安装Microsoft MDAC。

7. ACCESS数据库的压缩
HOWTO: Compacting Microsoft Access Database via ADO
Q230501

[code]
#import "C:\PROGRAM FILES\COMMON FILES\System\ado\MSJRO.DLL" no_namespace

try
{

IJetEnginePtr jet(__uuidof(JetEngine));

jet->CompactDatabase(

"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=d:\\nwind2.mdb",

"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=d:\\abbc.mdb;" \

"Jet OLEDB:Engine Type=4");

}

catch(_com_error &e)

{

::MessageBox(NULL, (LPCTSTR)e.Description( ), "", MB_OK) ;

}
[/code]

另外,DAO的方法
[code]
try
{

CDaoWorkspace::CompactDatabase( _T( "C:\\DB1.MDB" ),

_T( "C:\\DB2.MDB" ), dbLangGeneral, dbVersion30,

_T( ";PWD=MyPassword" ) );

}

catch( CDaoException* e )

{

AfxMessageBox( e->m_pErrorInfo->m_strDescription );

e->Delete();

}
[/code]

不过此时注意MFC 6.0 可以使用 Microsoft(R) Access 2000 数据库。若要在应用程序中使用此功能,必须使用下例方法启用 DAO 3.6:

在进行任何与数据库相关的调用前,链接 DLL 版的 MFC,并将下列行添至 InitInstance 中:
AfxGetModuleState()->m_dwVersion = 0x0601

在 _MFC_VER 设置为 0x0601 后重新编译 MFC 静态库。

8.
使用ADO调用存储过程

来源不详

在ADO中调用存储过程一直是一个困扰大家的问题。其实,关于ADO调用存储过程的方法在很多书中都有讲到,标准的做法无非是按照以下步骤进行:
1、生成并初始化一个_CommandPtr对象;
2、生成调用存储过程需要的参数,这些参数都是_ParameterPtr对象;
3、按照顺序将使用_CommandPtr的Append方法为存储过程提供参数(包括输入参数和输出参数);
4、为_CommandPtr对象指定需要使用的ADO连接;
5、使用_CommandPtr的Execute方法调用存储过程;
6、从结果中获取返回参数的值(如果有的话)。
具体的过程在此我不详细描述,我想看看本文附带的代码就应该很明白了。
在这里我想就我使用ADO调用存储过程时的一些体会说明一下。
1、关于CreateParameter函数
该函数的原型为:CreateParameter (Name, Type, Direction, Size, Value)
其中Name是参数的名称,可以指定也可以不指定;
Type是一个DataTypeEnum值,指定参数的类别,取值有adInteger(整型)、adChar(字符/字符串型)等;
Direction是一个ParameterDirectionEnum值,其取值为adParamInput、adParamInputOutput、 adParamOutput、adParamReturnValue、adParamUnknown;
Size是一个Long类型的值,指示该参数值以字节计算的最大长度,例如对int型,该值可以取为sizeof(int),对Long型,该值可以取为sizeof(long),对字符串型,可以使用该字符串的长度;
Value是一个variant类型的值,是该参数的取值。
在这里需要注意的是,Type参数、Direction参数以及Size参数一定要和存储过程定义时的参数相吻合,
例如,如果有下面一个存储过程
CREATE PROCEDURE SMS_Proc_Handle_All (@UserID Integer, @SourAddr Varchar(15), @DestAddr varchar(5000), @AvValue Single output, @ReturnInfo varchar(100) output )
则Type参数的取值依次为adInteger、adChar、adChar、adSingle,adChar;
Direction参数的取值依次为adParameterIn、adParameterIn、adParameterIn、adParameterOut、adParameterOut;
对于输入参数,Size的值可以根据实际数值来定,对于输出参数,最好是根据定义确定(上例中ReturnInfo参数的Size值可以取为100)。
2,关于获取Output的参数
获取ourput参数是大家最关注的问题,同时也是最“难”的问题,因为按照书本上的写法,经常获得不了Output参数,其实这个问题很容易解决:在调用_CommandPtr的Execute方法时,写成
cmmd->Execute(NULL, NULL, adCmdStoredProc);
而不要写成 RecordsetPtr rec = cmmd->Execute(NULL, NULL, adCmdStoredProc);
也就是说,不取返回值(我不知道这是为什么,但是相信我,事情就是这样)。
这句执行完后,使用 cmmd->Parameters->GetItem("XXXXXX")->GetValue(); ^^^^^^^
输出参数的名称 就可以获得输出参数的值了。
以下是一个通过ADO调用存储过程的部分代码:
_CommandPtr cmmd; HRESULT hr = cmmd.CreateInstance(__uuidof(Command));
if(FAILED(hr))
{ AfxMessageBox("NewNetDatabase()中创建_CommandPtr对象失败"); return 0; }
_ParameterPtr param;
param = cmmd->CreateParameter(""/*NetType*/,adTinyInt, adParamInput, sizeof(BYTE),(BYTE)(m_nNetType+1));
cmmd->Parameters->Append(param);
param = cmmd->CreateParameter(""/*Name*/,adVarChar, adParamInput, m_strName.GetLength()+1, _variant_t(m_strName));
cmmd->Parameters->Append(param);
param = cmmd->CreateParameter(""/*Desp*/,adVarChar, adParamInput, m_strDesp.GetLength()+1, _variant_t(m_strDesp));
cmmd->Parameters->Append(param);
param = cmmd->CreateParameter("NewNetID"/*NetID*/,adInteger, adParamOutput, sizeof(long), (long)m_nNewNetID);//返回参数,返回新建的网络的ID cmmd->Parameters->Append(param);
cmmd->CommandText=_bstr_t("GSDT_NewNet");//存储过程的名称
cmmd->ActiveConnection = m_pConPtr;//需要使用的ADO连接
cmmd->CommandType=adCmdStoredProc;
//注意下面的一行代码,如果你写成这样,就获得不了返回参数的值
//_RecordsetPtr rec = cmmd->Execute(NULL, NULL, adCmdStoredProc);
//我不知道这是为什么,但事实就是这样:)
cmmd->Execute(NULL, NULL, adCmdStoredProc);
m_nNewNetID=(long)cmmd->Parameters->GetItem("NewNetID")->GetValue();//通过参数返回值
cmmd.Detach();



<< Home

This page is powered by Blogger. Isn't yours?