I am investigating several Visual Studio 2015 C++ project types which use ADO to access an SQL Server database. The simple example performs a select against a table, reads in the rows, updates each row, and updates the table.
The MFC version works fine. The Windows console version is where I am having a problem updating the rows in the recordset. The update()
method of the recordset is throwing a COM exception with the error text of:
L"Item cannot be found in the collection corresponding to the requested name or ordinal."
with an HRESULT
of 0x800a0cc1
.
In both cases I am using a standard ADO recordset object defined as;
_RecordsetPtr m_pRecordSet; // recordset object
In the MFC version, the function to update the current row in the recordset is:
HRESULT CDBrecordset::UpdateRow(const COleVariant vPutFields, COleVariant vValues)
{
m_hr = 0;
if (IsOpened()) {
try {
m_hr = m_pRecordSet->Update(vPutFields, vValues);
}
catch (_com_error &e) {
_bstr_t bstrSource(e.Description());
TCHAR *description;
description = bstrSource;
TRACE2(" _com_error CDBrecordset::UpdateRow %s %s\n", e.ErrorMessage(), description);
m_hr = e.Error();
}
}
if (FAILED(m_hr))
TRACE3(" %S(%d): CDBrecordset::UpdateRow() m_hr = 0x%x\n", __FILE__, __LINE__, m_hr);
return m_hr;
}
This function is called by using two COleSafeArray
objects composed into a helper class to make it easier to specify the column names and values to be updated.
// Create my connection string and specify the target database in it.
// Then connect to SQL Server and get access to the database for the following actions.
CString ConnectionString;
ConnectionString.Format(pConnectionStringTemp, csDatabaseName);
CDBconnector x;
x.Open(_bstr_t(ConnectionString));
// Create a recordset object so that we can pull the table data that we want.
CDBrecordset y(x);
// ....... open and reading of record set deleted.
MyPluOleVariant thing(2);
thing.PushNameValue (SQLVAR_TOTAL, prRec.lTotal);
thing.PushNameValue (SQLVAR_COUNTER, prRec.lCounter);
hr = y.UpdateRow(thing.saFields, thing.saValues);
Because the Windows Console version is not using MFC, I am running into some definition issues which appear to be due to ATL COM class CComSafeArray
being a template.
In the MFC source, COleSafeArray
is a class derived from tagVARIANT
which is a union
that is the data structure for a VARIANT
. However in ATL COM, CComSafeArray
is a template that I am using as CComSafeArray<VARIANT>
which seems reasonable.
However when I try to use a variable defined with this template, a class CDBsafeArray
derived from CComSafeArray<VARIANT>
, I get the following compilation error at the point where I call m_pRecordSet->Update()
:
no suitable user-defined conversion from "const CDBsafeArray" to "const _variant_t" exists
_variant_t
seems to be a wrapper class for VARIANT
and there does not seem to be a conversion path between CComSafeArray<VARIANT>
and _variant_t
however there is a conversion path between COleSafeArray
and _variant_t
.
What I have tried is to specify the m_psa
member of the class which is a SAFEARRAY
of type VARIANT
and this compiles however I see the COM exception above when testing the application. Looking in the object with the debugger, the object specifying the fields to be updated appears to be correct.
So it appears I am mixing incompatible classes. What would be a SAFEARRAY
wrapper class that will work with _variant_t
?
A Brief Overview of Microsoft VARIANT
and SAFEARRAY
The VARIANT
type is used to create a variable that may contain a value of many different types. Such a variable may be assigned an integer value at one point and a string value at another. ADO uses VARIANT
with a number of different methods so that the values read from a database or written to a database can be provided to the caller through a standard interface rather than trying to have lots of different, data type specific interfaces.
Microsoft specifies the VARIANT
type which is represented as a C/C++ struct
which contains a number of fields. The two primary parts of this struct
are a field that contains a value representing the type of the current value stored in the VARIANT
and a union of the various value types supported by a VARIANT
.
In addition to VARIANT
another useful type is SAFEARRAY
. A SAFEARRAY
is an array which contains array management data, data about the array such as how many elements it contains, its dimensions, and the upper and lower bounds (the bounds data allows you to have arbitrary index ranges).
The C/C++ source code for a VARIANT
looks something like the following (all of the component struct
and union
members seem to be anonymous, e.g. __VARIANT_NAME_2
is #defined
to be empty):
typedef struct tagVARIANT VARIANT;
struct tagVARIANT
{
union
{
struct __tagVARIANT
{
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
union
{
LONGLONG llVal;
LONG lVal;
BYTE bVal;
SHORT iVal;
// ... lots of other fields in the union
} __VARIANT_NAME_2;
DECIMAL decVal;
} __VARIANT_NAME_1;
} ;
COM uses the VARIANT
type in COM object interfaces to provide the ability to pass data through the interface back and forth and doing any kind of data transformation needed (marshaling).
The VARIANT
type supports a large variety of data types one of which is SAFEARAY
. So you can use a VARIANT
to pass a SAFEARRAY
over an interface. Rather than having an explicit SAFEARRAY
interface you can instead specify a VARIANT
interface that will recognize and process a VARIANT
that contains a SAFEARRAY
.
There are several functions provided to manage the VARIANT
type some of which are:
VariantInit()
VariantClear()
VariantCopy()
And there are several functions provided to manage the SAFEARRAY
type some of which are:
SafeArrayCreate()
SafeArrayCreateEx()
SafeArrayCopyData();
Three different Microsoft VARIANT
classes: MFC, ATL, Native C++
Microsoft has provided several different frameworks over the years and one of the goals of these frameworks and libraries has been the ability to work easily with COM objects.
We will look at three different versions of VARIANT classes for C++ in the following: (1) MFC, (2) ATL, and (3) what Microsoft calls native C++.
MFC is a complex framework developed early in the C++ life to provide a very comprehensive library for Windows C++ programmers.
ATL is a simpler framework developed to assist people creating COM based software components.
The _variant_t
seems to be a standard C++ class wrapper for VARIANT
.
The ADO _RecordsetPtr
class has the Update()
method that accepts a _variant_t
object which looks like:
inline HRESULT Recordset15::Update ( const _variant_t & Fields, const _variant_t & Values ) {
HRESULT _hr = raw_Update(Fields, Values);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _hr;
}
MFC provides a set of classes for working with COM objects with the classes for the VARIANT
type being COleVariant
and COleSafeArray
. If we look at the declaration for these two classes we see the following:
class COleVariant : public tagVARIANT
{
// Constructors
public:
COleVariant();
COleVariant(const VARIANT& varSrc);
// .. the rest of the class declaration
};
class COleSafeArray : public tagVARIANT
{
//Constructors
public:
COleSafeArray();
COleSafeArray(const SAFEARRAY& saSrc, VARTYPE vtSrc);
// .. the rest of the class declaration
};
If we look at the ATL versions of these classes what we find is CComVariant
and CComSafeArray
however CComSafeArray
is a C++ template. When you declare a variable with CComSafeArray
you specify the type of the values to be contained in the underlying SAFEARRAY
structure. The declarations look like:
class CComVariant : public tagVARIANT
{
// Constructors
public:
CComVariant() throw()
{
// Make sure that variant data are initialized to 0
memset(this, 0, sizeof(tagVARIANT));
::VariantInit(this);
}
// .. other CComVariant class stuff
};
// wrapper for SAFEARRAY. T is type stored (e.g. BSTR, VARIANT, etc.)
template <typename T, VARTYPE _vartype = _ATL_AutomationType<T>::type>
class CComSafeArray
{
public:
// Constructors
CComSafeArray() throw() : m_psa(NULL)
{
}
// create SAFEARRAY where number of elements = ulCount
explicit CComSafeArray(
_In_ ULONG ulCount,
_In_ LONG lLBound = 0) : m_psa(NULL)
{
// .... other CComSafeArray class declaration/definition
};
The _variant_t class is declared as follows:
class _variant_t : public ::tagVARIANT {
public:
// Constructors
//
_variant_t() throw();
_variant_t(const VARIANT& varSrc) ;
_variant_t(const VARIANT* pSrc) ;
// .. other _variant_t class declarations/definition
};
So what we see is a small difference between how the three different frameworks (MFC, ATL, and native C++) do VARIANT
and SAFEARRAY
.
Using the Three VARIANT
Classes Together
All three have a class to represent a VARIANT
which is derived from the struct tagVARIANT
which allows all three to be used interchangeable across interfaces. The difference is how each handles a SAFEARRAY
. The MFC framework provides COleSafeArray
which derives from struct tagVARIANT
and wraps the SAFEARRAY
library. The ATL framework provides CComSafeArray
which does not derive from struct tagVARIANT
but instead uses composition rather than inheritance.
The _variant_t
class has a set of constructors which will accept a VARIANT
or a pointer to a VARIANT
as well as operator methods for assignment and conversion that will accept a VARIANT
or pointer to a VARIANT
.
These _variant_t
methods for VARIANT
work with the ATL CComVariant
class and with the MFC COleVariant
and COleSafeArray
classes because these are all derived from struct tagVARIANT
which is VARIANT
. However the ATL CComSafeArray
template class does not work well with _variant_t
because it does not inherit from struct tagVARIANT
.
For C++ this means that a function that takes an argument of _variant_t
can be used with the ATL CComVariant
or with the MFC COleVariant
and COleSafeArray
but can not be used with an ATL CComSafeArray
. Doing so will generate a compiler error such as:
no suitable user-defined conversion from "const ATL::CComSafeArray<VARIANT, (VARTYPE)12U>" to "const _variant_t" exists
See User-Defined Type Conversions (C++) in the Microsoft Developer Network documentation for an explanation.
The simplest work around for a CComSafeArray
seems to be to define a class that derives from CComSafeArray
and to then provide a method that will provide a VARIANT
object that wraps the SAFEARRAY
object of the CComSafeArray
inside of a VARIANT
.
struct CDBsafeArray: public CComSafeArray<VARIANT>
{
int m_size;
HRESULT m_hr;
CDBsafeArray(int nSize = 0) : m_size(nSize), m_hr(0)
{
// if a size of number of elements greater than zero specified then
// create the SafeArray which will start out empty.
if (nSize > 0) m_hr = this->Create(nSize);
}
HRESULT CreateOneDim(int nSize)
{
// remember the size specified and create the SAFEARRAY
m_size = nSize;
m_hr = this->Create(nSize);
return m_hr;
}
// create a VARIANT representation of the SAFEARRAY for those
// functions which require a VARIANT rather than a CComSafeArray<VARIANT>.
// this is to provide a copy in a different format and is not a transfer
// of ownership.
VARIANT CreateVariant() const {
VARIANT m_variant = { 0 }; // the function VariantInit() zeros out so just do it.
m_variant.vt = VT_ARRAY | VT_VARIANT; // indicate we are a SAFEARRAY containing VARIANTs
m_variant.parray = this->m_psa; // provide the address of the SAFEARRAY data structure.
return m_variant; // return the created VARIANT containing a SAFEARRAY.
}
};
This class would then be used to contain the field names and the values for those fields and the ADO _RecordsetPtr
method of Update()
would be called like:
m_hr = m_pRecordSet->Update(saFields.CreateVariant(), saValues.CreateVariant());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With