本文告诉你怎样使ADO在你未来的应用中更简单。但是,首先,先向你展示使用ADO的另外一些细节(在你的ATL COM对象中或在你的应用程序中)。例子中,我会向你展示如何在ATL对象中使用ADO。我们使用ATL建立business层对象的所有数据IO和规则。
这样不但允许我们在特定的应用中使用这些规则和数据,而且另外一些“相关”的应用程序可以使用它们。以下是本文接触到的内容:
1、建立一个ATL对象。
2、在ATL对象中加入ADO。
3、在接口中加入方法和属性。
4、BSTR(_bstr_t), VARIANT(_variant_t), 和 SAFEARRAY。
5、使用Connection对象连接/关闭连接,从SQL建立记录集,使用Command对象。
6、使用Command对象执行存储程序。
7、将记录集转换称符号分割字符串,并传送给客户程序。
8、封装访问ADO的business逻辑应用。
9、展示VC++和VB如何使用COM对象的方法和属性。
完美的ADO II
by:Bob Place 1999.6.27
from:codeGuru
翻译:snoopy
一年多以前,我写了通用的Recordset。这是一个简单的包封了CDaoRecordset的类,令使用动态绑定大大简单化。然后,ADO来了,它缺省地使用动态绑定,跟DAO和通用的Recordset说了GOODBYE。为了帮助别人认识到使用ADO是多么容易,也是因为缺少ADO和VC的文档,我写了"完美的ADO"。现在,我想更进一步。本文告诉你怎样使ADO在你未来的应用中更简单。但是,首先,先向你展示使用ADO的另外一些细节(在你的ATL COM对象中或在你的应用程序中)。例子中,我会向你展示如何在ATL对象中使用ADO。我们使用ATL建立business层对象的所有数据IO和规则。
这样不但允许我们在特定的应用中使用这些规则和数据,而且另外一些“相关”的应用程序可以使用它们。以下是本文接触到的内容:
1、建立一个ATL对象。
2、在ATL对象中加入ADO。
3、在接口中加入方法和属性。
4、BSTR(_bstr_t), VARIANT(_variant_t), 和 SAFEARRAY。
5、使用Connection对象连接/关闭连接,从SQL建立记录集,使用Command对象。
6、使用Command对象执行存储程序。
7、将记录集转换称符号分割字符串,并传送给客户程序。
8、封装访问ADO的business逻辑应用。
9、展示VC++和VB如何使用COM对象的方法和属性。
正如你可判断的,这是一篇很长的文章。我不能覆盖所有的东西,虽然我很想这样。因此如果你想得到更详细的细节,我会向你介绍别的参考文章。
使用ATL建立通用ADO COM对象:
我们要做的第一件事是建立一个ATL对象。使用向导很容易建立一个ATL。我们将通用ADO作为一个EXE,但是你可以选择DLL。选择EXE的优点是它不会扰乱你的客户端应用程序,因为他们在不同的进程空间。但是你认为你的客户端应用程序不会被扰乱,可以选择DLL。如果你是一个像我
这样的人,我建议选择EXE。
1、打开ATL COM向导,命名未MyADO(或者另外你喜欢的名字),按OK。
2、选择服务器类型,EXE或DLL。完成。
这样你就建立了一个COM对象(虽然一点用也没有)。现在我们必须给它加一个接口。
1、在Insert菜单中,选择New ATL Object。
2、双击Simple Object图标。然后会弹出一个对话框。
3、输入名字为ADO。
4、在Attributes中,看看里面的内容,只须装着明白点点头,但千万不要修改任何东西。
5、按OK。
好,你已经建立了含有CADO类和IADO接口的MyADO COM对象。CADAO类是一个c++类,因此你可以像你所熟悉的其它一些类一样使用它。IADO只是你的COM客户端可以看到的东西而已。当你建立了一个MyADO对象,你可以使用所有加到这个接口的内容。后面我们会看得更清楚,现在重要的只是记住接口有两个重要的部分:它们是属性和方法。使用属性和方法的例子:
Property
(VB)
MyADOObject.SomeProperty = 10
(VC)
MyADOObject->put_SomeProperty(10);
Method
(VB)
Counter = MyADOObjcet.GetInt("SELECT COUNT(*) FROM some_table")
(VC)
MyADOObjcet->GetInt("SELECT COUNT(*) FROM some_table",&Counter)
在使用COM方法时,VB和VC的一个不同的地方是:用VC访问的时候,没有缺省的参数,就是说,如果方法有5个参数,那么你必须提供5个参数;
而使用VB可以跳过你不需要的参数。在我的COM对象中,我常有一个全局的变量:
VARIANT m_vNull;
然后,在构造器中初始化:
m_vNull.vt=VT_NULL;
m_vNull.scode = DISP_E_PARAMNOTFOUND;
这个变量可以在你想使用缺省参数的时候使用。
我们有了一个简单的含有一个接口的COM对象。现在,我们需要加入ADO。和在应用程序中使用ADO一样,你必须在stdfx.h中加入导入ADO的代码
。因为我将ADO的路径在包含文件中(TOOLS->Options->Directories Tab),因此我们只需加入
#import "msado15.dll" no_namespace rename("EOF","adoEOF")
如果你没有将ADO的路径包含进去,你必须将上面的语句该成ADO文件的完整路径。具体可参照“完美的ADO”。
我们要做的第一件事情是建立_ConnectionPtr,_CommandPtr, 和 _RecordsetPtr的一个全局的实例:
1、右击CADO;
2、选择Add Member Variable;
3、变量类型是_ConnectionPtr,变量名是m_Connection。
4、同样的方法:_CommandPtr, m_Command 和 _RecordsetPtr , m_Recordset。
因为我们的例子一次只连接一个数据库,因此你可以使用一个全局的Connection和一个全局的Command。记住:Connection对象用来连接数据库
;Command对象用来执行存储程序。通常每个COM session有一个Connection、Command、Recordset就够了。也不是说你不可以加入多一些。但
是我们发现简单最完美。
现在我们在构造器中初始化它们:
m_Connection.Createinstance(__uuidof(Connection));
m_Command.Createinstance(__uuidof(Command));
m_Recordset.CreateInstance(__uuidof(Recordset));
我们也必须在释构器中释放它们:
try
{
if(m_Connection->State == adOpen)
{
// the connection is still open so we need to close it
m_Connection->Close();
}
if(m_Recordset->State == adOpen)
{
// the connection is still open so we need to close it
m_Recordset->Close();
}
m_Connection = NULL;
m_Command = NULL;
m_Recordset = NULL;
}
catch(_com_error &e){} // error handling here
catch(...){}// all other exceptions here
注意到我们在将它们赋值为NULL之前,检查确认connection和recordset没有打开。我们要在COM服务器中加入的东西就这么多了。现在我们要
决定我们的COM对象能做点什么了。但首先,让我们看看SAFEARRAYS, VARIANTS, 和 BSTRS。我们必须经常使用它们,因此我们必须理解它们是
如何工作的。(你可能也想到标准string和strstream模板)。因为我们喜欢简单,并且我们的COM服务器的目的是可以被任何语言的客户端使
用。因此我们只使用SAFEARRAYS, VARIANTS, 和 BSTRS,这样我们的生活就会更简单。
BSTR(和封装了的_bstr_t)是一个字符串和一个包含字符串大小的数。使用它们很简单,但你必须为它们分配和释放内存。有很多种方法分配
和释放内存,最常用的一种方法是:使用::SysAllocString和::SysFreeString。我喜欢使用_bstr_t,使用它你可以完全忽略分配和释放内存
,因为它为你做了。
#include <comdef.h> //_bstr_t 所在的地方
_bstr_t BstrHolder("this is a BSTR");
你想要char *,只需简单地使用类型转换即可:
char *holder = (char*)BstrHolder;
我们使用它来和COM对象传递信息。你可以将它转换成你想要的类型:char *,CString等。我们将只使用它来和COM对象传递信息(字符串信息
)。
让我们也考虑一下BSTR和内存分配和释放的问题。应该是谁分配内存,又是谁负责释放内存呢?规则相当简单:
1、如果参数是[in],那么客户端必须分配和释放内存。COM服务器不能改变它。
2、如果参数是[out],那么客户端必须分配内存,COM服务器可以从新分配,但客户端负责释放。
3、如果是COM内建变量,COM对象负责分配和释放。
VARIANT(和它的封装类_variant_t)是大量的类型的联合。使用_variant_t很容易转换类型。VARIANT被广泛使用在VB、和scripting语言中。
一旦你习惯了它,它在c++中也很有用。在和COM通讯时,当使用的不是简单的数据类型,或我们希望返回的数据可能是几个不同的东西(例如
它可能是一个字符串或一个错误号码,可以检查VARIANT的vt属性以得到数据类型)。让我们看看如何用_variant_t建立一个VARIANT。
#include <comdef.h> // this is where the _bstr_t is
_bstr_t BstrHolder("this is a Bstr");
_variant_t Holder(BStrHolder)
或者:
_variant_t Holder;
Holder = BstrHolder;
// 然后获取char *
char * cHolder = (char*)_bstr_t(Holder);
基本上我们先建立一个_bstr_t,然后将它送入_variant_t,然后使用类型转换回_bstr_t。
(未完待续)