snoopy每日一译-完美的ADO[2](中)
by:Bob Place 1999.6.27
from:codeGuru
翻译:snoopy
SAFEARRAY(sorry没有它的封装类)是从容易COM对象传送和接受数据的方法之一(当然,容易是相对而言)。
SAFEARRAYBOUND可以用来设置SAFEARRY的范围。你必须设定上界和下界。
让我们看看例子,假设我们要建立一个10行2列的SAFEARRAY:
//2列,因此MyBound有2维
SAFEARRAYBOUND MyBound[2];
//10行
MyBound[0].cElements = 10;
MyBound[1].cElements = 10;
//设定下界为0
MyBound[0].lLbound = 0;
MyBound[1].lLBound = 0;
//定义VARIANT指针
VARIANT *pVariant;
VariantInit(pVariant);
pVariant->vt = VT_VARIANT | VT_ARRAY;
pVariant->parray = SafeArrayCreate(VT_VARIANT2MyBound);//建立SAFEARRAY
或者你可以直接使用SAFEARRAY类型:
SAFEARRAY pArray = SafeArrayCreate(VT_VARIANT2MyBound);
现在,让我们给SAFEARRAY填充一些内容:
VARIANT Column1(SysAllocString("COW");
VARIANT Column2(100);
int MyPosition[2];
MyPosition[0] = 0;// 0列
MyPosition[1] = 0;// 0行
SafeArrayPutElement(pVariant->parrayMyPosition&Column1);
MyPosition[0] = 1;//1列
MyPosition[1] = 0;//0 行
SafeArrayPutElement(pVariant->parrayMyPosition&Column2);
现在,让我们回到COM组件中。
我们需要什么方法呢?
这个问题轮到你回答。在本文中,我只覆盖了几种方法,你可以加入你能想到的任何方法。首先让我们来考虑打开和关闭连接的方法:
打开一个连接:右击IADO选择New Method,参数如下:
[in] BSTR ConnectionString [in] BSTR UserId [in] BSTR Password [outretval] int *Result
[in]的意思是参数从客户端传输过来,[out]的意思是从服务器端送到客户端,[retval]的意思是对于VB等语言的客户端来说,返回值。
STDMETHODIMP CADO::ADOOpenConnection(BSTR ConnectionString BSTR UserId BSTR Password int *Result)
{
_bstr_t TempConnectionString(ConnectionString);
if(TempConnectionString.length() <1)
{
// 如果没有连接串,连接默认数据库
// It will change with each ADO COM Object
std::string Holder = "MY_DSN";
// Now open the connection only if it is not already open
try
{
if(m_Connection->State != adStateOpen)
{
HRESULT hr = m_Connection->Open(_bstr_t(Holder.c_str())_bstr_t("Admin")_bstr_t("ORAIDERS")-1);
if(hr != S_OK)
{
Result = 0;
return S_FALSE;
}
}
}
catch(_com_error &e){} // error handling here
catch(...){}// all other exceptions here
return S_OK;
}
else
{
try
{
m_Connection->Open(ConnectionStringUserIdPasswordadAsyncConnect);
}
catch(_com_error &e){} // error handling here
catch(...){}// all other exceptions here
return S_OK;
}
}
注意到我们使用了std string模板,另外我们检查可能出现的错误。当发生错误的时候,你应该向客户端发送恰当的消息。在本文中,因为我很懒,所以只有最简单的捕抓错误语句。调用这个方法:
(VB)
Result = MyADOObject.ADOOpenConnection """"""
(VC)
MyADOObject->ADOOpenConnection(""""""&Result);
同样地加入关闭连接的方法:
STDMETHODIMP CADO::ADOCloseConnection()
{
try
{
if(m_Connection->State == adStateOpen)
m_Connection->Close();
}
catch(_com_error &e){} // error handling here
catch(...){}// all other exceptions here
return S_OK;
}
现在,让我们考虑做点实际的工作。我们决定用3种方法从COM对象获取数据。第一种是返回记录集,第二种返回一个符号分割字符串,第三种返回单一的一个值。我们还决定可以使用SQL和存储程序两种方式来获取数据。因此,我们需要添加如下的方法:
ADOExecuteReturnDelimited)(/*[in]*/ BSTR SQL /*[in]*/ BSTR Delimiter /*[outretval]*/VARIANT *Result);
ADOExecute)(/*[in]*/ BSTR SQL /*[outretval]*/ _Recordset **Result);
ADOExecuteSPReturnDelimited)(/*[in]*/ BSTR SPName /*[in]*/ VARIANT ParameterArray /*[in]*/BSTR Delimiter /*[outretval]*/ VARIANT *Result);
ADOExecuteSP)(/*[in]*/ BSTR SPName /*[in]*/ VARIANT ParameterArray /*[outretval]*/ _Recordset **Result);
ADOGetVariant)(/*[in]*/ BSTR SQL /*[outretval]*/ VARIANT *Result);
添加这些方法,你只需右击接口,然后选择New Method…。但是,且慢…,当你试图编译它们的时候,你会得到一些发生了错误的信息,像:没有_Recordset类型等等。改正这些错误,你需要加入以下一些东西到你的程序中:
首先:Helper.h文件,包含下列语句:
struct _Recordset;
#if !defined(__cplusplus) || defined(CINTERFACE)
typedef struct _Recordset _Recordset;
#endif
其次:建立一个helper.idl文件,包含如下语句:
import "msado15.idl";
然后:在你的ADO.h中加入:
#include "helper.h"
最后:在你的项目的idl文件中,加入:
import "helper.idl";
让我们看看以下代码:
STDMETHODIMP CADO::ADOExecute(BSTR SQL _Recordset **Result)
{
VARIANT RecordsEffected;
RecordsEffected.vt = VT_INT;
try
{
if(m_Recordset == NULL)
m_Recordset.CreateInstance(__uuidof(Recordset));
if(m_Recordset->State == adStateOpen)
m_Recordset->Close();
m_Recordset = m_Connection->Execute(_bstr_t(SQL)&RecordsEffectedadCmdText);
*Result = m_Recordset.Detach();
}
catch(_com_error &e){} // error handling here
catch(...){}// all other exceptions here
}
首先,我们做的第一件事情是确认m_Recordset可用并关闭:
if(m_Recordset == NULL)
m_Recordset.CreateInstance(__uuidof(Recordset));
if(m_Recordset->State == adStateOpen)
m_Recordset->Close();
然后使用m_Connection执行SQL语句:
m_Recordset = m_Connection->Execute(_bstr_t(SQL)&RecordsEffectedadCmdText);
最后:
*Result = m_Recordset.Detach();
客户端使用:
(VB)
Dim MySet as Object
Object = MyADOObject.ADOExecute("SELECT * FROM some_table")
(VC)
_RecordsetPtr MySet;
MyADOObject->ADOExecute(_bstr_t("SELECT * FROM some_table)&MySet);
这些方法很简单,我们所做的只是将连接、执行和返回包封起来。你或许会问:实际上简单地在客户端建立连接并执行SQL语句不就完了吗?干嘛费那么大的劲?
我们要记住:我们要建立的一条道路是让客户端不知道我们的数据如何组织,在COM中怎样访问数据。以上建立的方法会用于COM对象里面,以方便编写一些特定的方法。你或许可以选择将它们隐藏,使客户端开发人员无法访问,只要你喜欢。
好,让我们继续:
STDMETHODIMP CADO::ADOExecuteReturnDelimited(BSTR SQL BSTR Delimiter VARIANT *Result)
{
VARIANT RecordsEffected;
RecordsEffected.vt = VT_INT;
*Result = _variant_t("");
try
{
_RecordsetPtr PopSet;
PopSet.CreateInstance(__uuidof(Recordset));
PopSet->CursorLocation = adUseClient;
if(m_Connection->State != adStateOpen)
{
*Result = _variant_t("VISP_COM_ERROR_CONNECTION_NOT_OPEN");
return S_FALSE;
}
ADOExecute((char*)_bstr_t(SQL)&PopSet);
if(!PopSet->adoEOF)
{
BSTR bstrResult = NULL;
_bstr_t btColDelim(L"");
_bstr_t btRowDelim(L"\r\n");
_bstr_t btNullExp(L"");
PopSet->raw_GetString(adClipString-1btColDelimbtRowDelimbtNullExp&bstrResult);
*Result = _variant_t(bstrResult);
}
else
*Result = _variant_t("");
}
catch(_com_error &e){} // error handling here
catch(...){}// all other exceptions here
return S_OK;
}
你可看到,我们在这使用了一个本地的_RecordsetPtr实例:
_RecordsetPtr PopSet;
PopSet.CreateInstance(__uuidof(Recordset));
PopSet->CursorLocation = adUseClient;
需要注意的是:GetString方法会抛出一个未处理异常,所以不要用GetString方法,你可以使用raw_GetString方法。我们使用了ADOExecute而不使用Connection. Execute方法,是因为可以少打几个字:
ADOExecute((char*)_bstr_t(SQL)&PopSet);
然后我们取回记录集,检查有没有记录,接着使用raw_GetString方法转换记录集为符号分割字符串:
if(!PopSet->adoEOF)
{
BSTR bstrResult = NULL;
_bstr_t btColDelim(L"");
_bstr_t btRowDelim(L"\r\n");
_bstr_t btNullExp(L"");
PopSet->raw_GetString(adClipString-1btColDelimbtRowDelimbtNullExp&bstrResult);
*Result = _variant_t(bstrResult);
}
else
*Result = _variant_t("");
现在你可以实验用比较的记录集转化到字符串中,我没有试过可用多打的记录集,如果谁试过,我很感兴趣这个大小是多少。客户端使用:
(VB)
ResultString = MyADOObject.ADOExecuteReturnDelimited("SELECT * FROM some_table""")
(VC)
BSTR ResultString;
MyADOObject->ADOExecuteReturnDelimited(_bstr_t("SELECT * FROM some_table")_bstr_t("")&Result);
好,现在我们已经建立了一个ATL对象,加入了ADO支持,加入客4个方法。所有这些都只是简单地包封了ADO方法。让我们考虑,加入你的几个应用程序需要一个customer name 和一个current balance。在数据库中,它们保存在两个表中:cust_info和cust_credit。其中重要的域是cust_info.cust_id cust_info.cust_name 和cust_credit.cust_balance。对于别的应用程序来说,它们需要知道表名,域名和cust_id。
如果我们将和数据库比较密切的信息放到隐藏到方法中:
STDMETHODIMP CADO::sGetCustomerNameAndBalance(BSTR CustKey VARIANT *Result)
{
// std stream used to create the SQL statement
std::strstream SQL;
// Put the BSTR into something we can deal with
_bstr_t Holder(CustKey);
SQL.str("");
SQL<<"SELECT a.cust_name b.cust_balance FROM cust_info as a cust_credit as b WHERE a.cust_id = '<<(char*)Holder<<" AND a.cust_key = b.cust_key";
ADOExecuteReturnDelimited(_bstr_t(SQL.str().c_str())_bstr_t("")Result);
return S_OK;
}
Whoa 简单吗?让我们看看我们做了些什么?首先,我们建立了一个strstream ,这是一个STD模板:
std::strstream SQL;
接着,我们将客户端从来的BSTR转换成__bstr_t。我总是喜欢用_bstr_t,因为它容易使用。当然还有其他方法可以使用(CcomBstr是其中之一),我喜欢_bstr_t,永远…。我们使用SQL语句获取信息,客户端无须知道:
SQL.str("");// this resets it to nothing
SQL<<"SELECT a.cust_name b.cust_balance FROM cust_info as a cust_credit as b WHERE a.cust_id = '<<(char*)_bstr_t(CustKey)<<" AND a.cust_key = b.cust_key";
最后,我们调用我们几分钟前建立的ADOExecuteReturnDelimited()方法:
ADOExecuteReturnDelimited(_bstr_t(SQL.str().c_str())_bstr_t("")Result);
现在我们有了一个方法,它向客户端返回重要的信息,但是客户端无须知道我们的数据库是怎样组织的。当然客户端要知道COM对象有这样一个方法,并且这个方法需要一个customer id。让我们看看VB和VC客户端如何使用:
(VB)
MyResult = MyADOObject.sGetCustomerNameAndBalance(SomeCustID)
(VC)
VARIANT MyResult;
MyADOObject.->sGetCustomerNameAndBalance(SomeCustIDMyResult);
现在,例子还相当简单。但是我想你可以看到了一个ADO COM对象是多么强大了吧。客户端不知道(也没有必要知道)怎么去连接数据库,什么表名,怎样使用记录集,怎样从记录集中抽取需要的信息。客户端只需要调用COM对象的方法,这个方法需要一个customer ID,并且会返回一个逗号分割的包含customer name 和 balance的字符串就够了。
(未完待续)