Имеем ATL компоненту, которая подключается к серверам БД в нескольких вариантах (ADO или напрямую через API БД) и возвращает данные в виде Recordset (disconnected, разумеется

). Всё сделано примерно, как при вызове Connection.Execute(), глюков пока не наблюдалось. Хотелось сделать тоже самое для параметризованных запросов, типа "SELECT * FROM address WHERE city = ? AND street = ?", передавая входные параметри запроса через ADODB.Command. При этом хочется, чтобы параметры тоже были обработаны правильно, включая направления adParamOutput и adParamReturnValue.
Особенно заинтересовал последний, ибо недавно понадобылось вызвать возвратить результат из встроенной проседуры, которая лежит в Oracle Package, а это не рекордсет, а просто номер (INTEGER aka long). Простой пример в VB или VC++ работает на ура, напримерь это:
#include <stdio.h>
#import "c:\program files\common files\system\ado\msado15.dll" rename ("EOF","adoEOF") no_namespace
#define RsITEM(rs,x) rs->Fields->Item[_variant_t(x)]->Value
.....
_ConnectionPtr pConn;
_CommandPtr pCmd;
_ParameterPtr pPrm;
_RecordsetPtr pRS;
try
{
pConn.CreateInstance(__uuidof(Connection))
pConn->ConnectionString = L"Provider=OraOLEDB.Oracle;Data Source=my_oracle;Persist Security Info=True;User ID=test;Password=test";
pConn->CursorLocation = adUseClient;
pConn->Open("", "", "", -1);
if (pConn->State == adStateOpen)
{
pCmd.CreateInstance(__uuidof(Command))
pPrm.CreateInstance(__uuidof(Parameter))
pPrm = pCmd->CreateParameter(L"retval", adInteger, adParamReturnValue, sizeof(long), 0L);
pCmd->Parameters->Append(pPrm);
pCmd->CommandText = L"{ ? = CALL VIES_API.VAT(0, 'lv', '90000084505', '1212') }";
pCmd->PutRefActiveConnection(pConn);
pRS = pCmd->Execute(NULL, NULL, adCmdText);
// Тут хаваем наш return параметр :)
long lRet = pCmd->GetParameters()->GetItem(0L)->GetValue();
if (pRS->GetState() == adStateOpen)
{
while(pRS->adoEOF == false)
{
printf("%d\n", long(RsITEM(pRS, 0L)));
pRS->MoveNext();
}
pRS->Close();
}
pCmd->PutRefActiveConnection(NULL); // disconnect connection
pConn->Close();
}
}
catch(_com_error &e)
{
_bstr_t bstrSource(e.Source());
_bstr_t bs = _bstr_t(" Error: ") + _bstr_t(e.Error()) + _bstr_t(" Msg: ")
+ _bstr_t(e.ErrorMessage()) + _bstr_t(" Description: ")
+ _bstr_t(e.Description());
MessageBox(0, bs, bstrSource, MB_OK);
}
.....
Но с компонентом начинается проблемы. Понятно, чтобы подать Command от VB в ATL и обратно, надо его передать через ByRef посредством [in,out] в MIDL, но в практике это не очень то работает. И чтобы было интереснее, работа с Command объектом произходит не в вызиваемой функции, а ещё пару етажей ниже — в виртуальнвх функциях. То есть получается такя цепочка:
---[ VIDDB.idl ]---
[
uuid(3845B14E-E963-497C-AF2B-EDB3510ABE0F),
version(1.0),
helpstring("VID IA connection pool, 1.0")
]
library VIDDB
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
importlib("c:\program files\common files\system\ado\msado15.dll"); // import ADO typelib
.....
[
object,
uuid(B2D05B37-EFBC-42B2-9FC0-D0EC85A9B9A6),
dual,
helpstring("Client session database connection interface"),
pointer_default(unique)
]
interface IPoolConnection : IDispatch
{
.....
[id(10), helpstring("Execute SQL command with parameters and return recordset, if available")]
HRESULT ExecuteCommand([in, out] _Command *ParamCommand, [out, optional] VARIANT *RecordsAffected, [in, defaultvalue(-1)] long Options, [out, retval] _Recordset **ppRS);
.....
---[ PoolConnection.h ]---
.....
// IPoolConnection
protected:
CConnection m_conn; // DB connection object
.....
public:
STDMETHOD(ExecuteCommand)(/*[in, out]*/ _Command *pCommand, /*[out, optional]*/ VARIANT *lpvAffected, /*[in, defaultvalue(-1)]*/ long lOptions, /*[out, retval]*/ _Recordset **ppRS);
.....
---[ PoolConnection.cpp ]---
// Execute SQL command with parameters and return recordset, if available.
STDMETHODIMP CPoolConnection::ExecuteCommand(_Command *pCommand, VARIANT *lpvAffected, long lOptions, _Recordset **ppRS)
{
m_conn.ClearErrors();
m_conn.ExecuteCommand(pCommand, lpvAffected, lOptions, ppRS);
return S_OK;
}
---[ Connection.h ]---
......
class CConnBase
{
....
public:
......
virtual BOOL ExecuteCommand(_CommandPtr pCmd, VARIANT *lpvAffected, long lOptions, _Recordset **ppRS) = 0;
......
class CConnection
{
protected:
CConnBase *m_pConn;
.....
public:
.....
BOOL ExecuteCommand(_CommandPtr pCmd, VARIANT *lpvAffected, long lOptions, _Recordset **ppRS);
.....
---[ Connection.cpp ]---
.....
BOOL CConnection::ExecuteCommand(_CommandPtr pCmd, VARIANT *lpvAffected, long lOptions, _Recordset **ppRS)
{
if (!CheckOpenConnection())
return FALSE;
if (!m_pConn->ExecuteCommand(pCmd, lpvAffected, lOptions, ppRS))
m_lErrors++;
return (m_lErrors == 0);
}
// Set/switch database connection.
BOOL CConnection::SetConnection(LPCTSTR lpszConnection)
{
ATLASSERT(lpszConnection != NULL);
// Do nothing, if connection is that same.
if (m_cinfo.GetConnection().Compare(lpszConnection) == 0)
return FALSE;
// Close and remove previous connection.
if (m_pConn != NULL)
{
if (IsConnected())
Close();
delete m_pConn;
m_pConn = NULL;
}
// Load connection settings.
if (!m_cinfo.Load(lpszConnection))
{
m_lErrors++;
return FALSE;
}
// Create connection by type from loaded connection info.
switch (m_cinfo.GetConnType()) {
case ConnTypeADO: // connection, using ADO
m_pConn = new CConnADO;
break;
case ConnTypeInformix: // direct Informix connection
m_pConn = new CConnIfx;
break;
default:
m_lErrors++;
return FALSE; // unknown connection type
}
m_pConn->SetConnection(&m_cinfo); // reference to connection info
m_pConn->SetOwner(m_strOwner); // set owner name
return TRUE;
}
.....
---[ ConnADO.h ]---
class CConnADO : public CConnBase
{
_ConnectionPtr m_pConn; // ADO connection instance
.....
public:
BOOL ExecuteCommand(_CommandPtr pCmd, VARIANT *lpvAffected, long lOptions, _Recordset **ppRS);
.....
};
---[ ConnADO.cpp ]---
// Execute ADO command with parameterized SQL statement.
BOOL CConnADO::ExecuteCommand(_CommandPtr pCmd, VARIANT *lpvAffected, long lOptions, _Recordset **ppRS)
{
_RecordsetPtr pRS = NULL;
BOOL bResult = FALSE;
// Register start of command execution.
long lCmdID = Pool.GetCommandManager()->CommandStart(m_pCInfo->GetConnection(), m_strOwner, (LPCTSTR)_bstr_t(pCmd->CommandText));
try
{
pRS.CreateInstance(__uuidof(Recordset));
// До этого места никаких проблем не наблюдается.
// А ВОТ В СЛЕДУЮЩЕЙ СТРОКЕ МЫ ЛЯЖЕМ - ПРИЧЁМ НАГЛУХО!
pCmd->ActiveConnection = m_pConn; // set active connection to command
pRS = pCmd->Execute(lpvAffected, NULL, lOptions);
// If records is returned (recordset is open), disconnect it from
// connection and return to caller.
if (pRS->State == adStateOpen)
{
pRS->PutRefActiveConnection(NULL); // disconnect recordset from connection
// Returned record count to caller.
if (lpvAffected != NULL)
*lpvAffected = _variant_t(pRS->RecordCount).Detach();
*ppRS = pRS.Detach(); // return recordset to caller
}
}
catch (const _com_error& e)
{
if (!GetProviderErrors())
CMessage::Forward(m_pCInfo->GetConnection(), m_strOwner, e);
}
catch (...)
{
CMessage::ForwardFromSystem(m_pCInfo->GetConnection(), m_strOwner);
}
// Register command ending state.
if (bResult)
Pool.GetCommandManager()->CommandEnd(m_pCInfo->GetConnection(), m_strOwner, lCmdID);
else
Pool.GetCommandManager()->CommandEnd(m_pCInfo->GetConnection(), m_strOwner, lCmdID, CmdFailed);
return bResult;
}
Из VB и ASP это визывается примерно так:
Dim cn As VIDDB.PoolConnection
Dim cmd As ADODB.Command
Dim prm As Parameter
Dim lRet As Long
Set cn = New VIDDB.PoolConnection
.....
Set cmd = New ADODB.Command
cmd.CommandText = "{ ? = CALL VIES_API.VAT(0, 'lv', '90000084505', '1212')}"
Set prm = cmd.CreateParameter("retval", adInteger, adParamReturnValue)
cmd.Parameters.Append prm
cn.ExecuteCommand cmd
Debug.Print "Returned parameter value: " & cmd.Parameters(0).Value & vbNewLine
В конце получается то, что задаём команде параметер(ры), потом всё красиво доходит до места в ConnADO.cpp, где надо указать ActiveConnection — и тут летим в catch (const _com_error& e) c
Code: 800a0bb9; Meaning: Unknown error 0x800A0BB9; Source: ADODB.Connection; Description: Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another.
То же мы получаем, если хочем что нибудь сделать с параметрами этой команды, напримерь почистить и задать новые. Создаётся впечатление, что входящая команда сейчас вроде только для чтения. Подумал и указал входящую команду повсюду как
_Command **ppCommand с последующим Attach() Detach() к _CommandPtr в конечной функции. Не помогло

Может кто нибудь знает