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
andSAFEARRAY
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 usesVARIANT
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 thisstruct
are a field that contains a value representing the type of the current value stored in theVARIANT
and a union of the various value types supported by aVARIANT
.In addition to
VARIANT
another useful type isSAFEARRAY
. ASAFEARRAY
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 componentstruct
andunion
members seem to be anonymous, e.g.__VARIANT_NAME_2
is#defined
to be empty):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 isSAFEARAY
. So you can use aVARIANT
to pass aSAFEARRAY
over an interface. Rather than having an explicitSAFEARRAY
interface you can instead specify aVARIANT
interface that will recognize and process aVARIANT
that contains aSAFEARRAY
.There are several functions provided to manage the
VARIANT
type some of which are:And there are several functions provided to manage the
SAFEARRAY
type some of which are: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 forVARIANT
.The ADO
_RecordsetPtr
class has theUpdate()
method that accepts a_variant_t
object which looks like:MFC provides a set of classes for working with COM objects with the classes for the
VARIANT
type beingCOleVariant
andCOleSafeArray
. If we look at the declaration for these two classes we see the following:If we look at the ATL versions of these classes what we find is
CComVariant
andCComSafeArray
howeverCComSafeArray
is a C++ template. When you declare a variable withCComSafeArray
you specify the type of the values to be contained in the underlyingSAFEARRAY
structure. The declarations look like:The _variant_t class is declared as follows:
So what we see is a small difference between how the three different frameworks (MFC, ATL, and native C++) do
VARIANT
andSAFEARRAY
.Using the Three
VARIANT
Classes TogetherAll three have a class to represent a
VARIANT
which is derived from thestruct tagVARIANT
which allows all three to be used interchangeable across interfaces. The difference is how each handles aSAFEARRAY
. The MFC framework providesCOleSafeArray
which derives fromstruct tagVARIANT
and wraps theSAFEARRAY
library. The ATL framework providesCComSafeArray
which does not derive fromstruct tagVARIANT
but instead uses composition rather than inheritance.The
_variant_t
class has a set of constructors which will accept aVARIANT
or a pointer to aVARIANT
as well as operator methods for assignment and conversion that will accept aVARIANT
or pointer to aVARIANT
.These
_variant_t
methods forVARIANT
work with the ATLCComVariant
class and with the MFCCOleVariant
andCOleSafeArray
classes because these are all derived fromstruct tagVARIANT
which isVARIANT
. However the ATLCComSafeArray
template class does not work well with_variant_t
because it does not inherit fromstruct tagVARIANT
.For C++ this means that a function that takes an argument of
_variant_t
can be used with the ATLCComVariant
or with the MFCCOleVariant
andCOleSafeArray
but can not be used with an ATLCComSafeArray
. Doing so will generate a compiler error such as: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 fromCComSafeArray
and to then provide a method that will provide aVARIANT
object that wraps theSAFEARRAY
object of theCComSafeArray
inside of aVARIANT
.This class would then be used to contain the field names and the values for those fields and the ADO
_RecordsetPtr
method ofUpdate()
would be called like: