_variant_t,COleVariant,CComVariant和VARIANT之间的使用差异,并使用SAFEARRAY变体 [英] usage differences between _variant_t, COleVariant, CComVariant, and VARIANT and using SAFEARRAY variations
问题描述
我正在调查几个使用ADO访问SQL Server数据库的Visual Studio 2015 C ++项目类型。简单示例对表执行select,读取行,更新每一行,并更新表。
MFC版本工作正常。 Windows控制台版本是我在更新记录集中的行时出现问题的位置。记录集的 update()
方法抛出一个具有错误文本的COM异常:
L无法在与请求的名称或序号相对应的集合中找到项目。
与 HRESULT
c> 0x800a0cc1 。
在这两种情况下,我使用的标准ADO记录集对象定义为:
_RecordsetPtr m_pRecordSet; //记录集对象
在MFC版本中,更新记录集中当前行的函数是:
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\\\
,e.ErrorMessage(),description);
m_hr = e.Error();
}
}
if(FAILED(m_hr))
TRACE3(%S(%d):CDBrecordset :: UpdateRow()m_hr = 0x%x \\\
,__FILE__,__LINE__,m_hr);
return m_hr;
}
此函数通过使用两个 COleSafeArray
对象组成辅助类,以便更容易指定要更新的列名称和值。
//创建我的连接字符串并在其中指定目标数据库。
//然后连接到SQL Server并访问数据库以执行以下操作。
CString ConnectionString;
ConnectionString.Format(pConnectionStringTemp,csDatabaseName);
CDBconnector x;
x.Open(_bstr_t(ConnectionString));
//创建一个记录集对象,以便我们可以拉取我们想要的表数据。
CDBrecordset y(x);
// .......打开和读取记录集已删除。
MyPluOleVariant东西(2);
thing.PushNameValue(SQLVAR_TOTAL,prRec.lTotal);
thing.PushNameValue(SQLVAR_COUNTER,prRec.lCounter);
hr = y.UpdateRow(thing.saFields,thing.saValues);
因为Windows控制台版本没有使用MFC,我遇到了一些定义问题,由于ATL COM类 CComSafeArray
是一个模板。
在MFC源中, COleSafeArray
是从 tagVARIANT
派生的类,它是一个 union
VARIANT
。但是在ATL COM中, CComSafeArray
是我使用的模板,因为 CComSafeArray< VARIANT>
p>
但是当我尝试使用这个模板定义的变量时,从派生出一个
,我在调用 CDBsafeArray
CComSafeArray< VARIANT> m_pRecordSet-> Update()
时收到以下编译错误:
没有从const CDBsafeArray到const _variant_t的适当的用户定义转换存在
_variant_t
似乎是一个包装类 VARIANT
并且在 CComSafeArray< VARIANT>
和 _variant_t
之间似乎没有转换路径,但是有一个转换路径 COleSafeArray
和 _variant_t
。
指定类的 m_psa
成员,其类型为 VARIANT $>的
SAFEARRAY
c $ c>然后这个编译,但是我看到COM异常上面的测试应用程序。使用调试器查看对象,指定要更新的字段的对象似乎是正确的。
所以看来我混合不兼容的类。什么是 SAFEARRAY
包装器类,将使用 _variant_t
?
VARIANT
类型用于创建可能包含许多不同类型的值的变量。这样的变量可以在一点分配一个整数值,在另一点分配一个字符串值。 ADO使用多种不同的方法使用 VARIANT
,以便从数据库读取或写入数据库的值可以通过标准接口提供给调用者,而不是尝试许多不同的数据类型特定接口。
Microsoft指定 struct
其中包含多个字段。 struct
的两个主要部分是一个字段,它包含一个表示存储在 VARIANT
和 VARIANT
支持的各种值类型的联合。
除了 VARIANT
另一个有用的类型是 SAFEARRAY
。 SAFEARRAY
是一个数组,包含数组管理数据,数组数据,例如它包含多少个元素,它的维数,上下边界(边界数据允许
VARIANT
的C / C ++源代码看起来像下面这样(所有组件 struct
和 union
似乎是匿名的,例如 __ VARIANT_NAME_2
为
#defined
为空):
typedef struct tagVARIANT VARIANT;
struct tagVARIANT
{
union
{
struct __tagVARIANT
{
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved2;
union
{
LONGLONG llVal;
LONG lVal;
BYTE bVal;
SHORT iVal;
// ...联合中的许多其他字段
} __VARIANT_NAME_2;
DECIMAL decVal;
} __VARIANT_NAME_1;
};
COM使用COM对象中的 VARIANT
接口,以提供通过接口来回传递数据并进行任何所需的数据转换(封送)的能力。
包含多个函数,用于管理包含 VARIANT
类型支持多种数据类型,其中之一是 SAFEARAY
。因此,您可以使用 VARIANT
通过接口传递 SAFEARRAY
。而不是有一个明确的 SAFEARRAY
接口,你可以改为指定一个 VARIANT
接口,将识别并处理
SAFEARRAY
的VARIANT VARIANT
类型中的一些是:
VariantInit $ b VariantClear()
VariantCopy()
SAFEARRAY
键入以下某些:
SafeArrayCreate b SafeArrayCreateEx()
SafeArrayCopyData();
这些年来,Microsoft提供了几个不同的框架,这些框架和库的目标之一
我们将在以下内容中讨论C ++的VARIANT类的三个不同版本:(1)MFC,(2)ATL,
MFC是一个在C ++生命早期开发的复杂框架,为Windows C ++程序员提供了一个非常全面的库。
ATL是一个更简单的框架,用于帮助人们创建基于COM的软件组件。
_variant_t
似乎是 VARIANT
的标准C ++类封装。
ADO _RecordsetPtr
类具有接受 _variant_t
对象的 Update()
它看起来像:
inline HRESULT Recordset15 :: Update(const _variant_t& Fields,const _variant_t&值){
HRESULT _hr = raw_Update(Fields,Values);
if(FAILED(_hr))_com_issue_errorex(_hr,this,__uuidof(this));
return _hr;
}
MFC提供了一组用于处理COM对象的类, VARIANT
键入 COleVariant
和 COleSafeArray
。如果我们看看这两个类的声明,我们看到如下:
class COleVariant:public tagVARIANT
{
//构造函数
public:
COleVariant();
COleVariant(const VARIANT& varSrc);
// ..其余的类声明
};
class COleSafeArray:public tagVARIANT
{
//构造函数
public:
COleSafeArray();
COleSafeArray(const SAFEARRAY& saSrc,VARTYPE vtSrc);
// ..其余的类声明
};
如果我们看看这些类的ATL版本,我们发现 CComVariant
和 CComSafeArray
但是 CComSafeArray
是一个C ++模板。当您使用 CComSafeArray
声明变量时,指定要包含在底层 SAFEARRAY
结构中的值的类型。声明如下:
class CComVariant:public tagVARIANT
{
//构造函数
public:
CComVariant()throw()
{
//确保变量数据初始化为0
memset(this,0,sizeof(tagVARIANT));
:: VariantInit(this);
}
// ..其他CComVariant类的东西
};
// SAFEARRAY的wrapper。 T是类型存储(例如BSTR,VARIANT等)
template< typename T,VARTYPE _vartype = _ATL_AutomationType< T> :: type>
class CComSafeArray
{
public:
//构造函数
CComSafeArray()throw():m_psa(NULL)
{
}
//创建SAFEARRAY,其中元素数量= ulCount
显式CComSafeArray(
_In_ ULONG ulCount,
_In_ LONG lLBound = 0):m_psa(NULL)
{
// ...其他CComSafeArray类声明/定义
};
_variant_t类声明如下:
class _variant_t:public :: tagVARIANT {
public:
//构造函数
//
_variant_t
_variant_t(const VARIANT& varSrc);
_variant_t(const VARIANT * pSrc);
// ..其他_variant_t类声明/定义
};
所以我们看到的是三个不同的框架(MFC,ATL和native C ++)do VARIANT
和 SAFEARRAY
。
有一个类来表示从 struct tagVARIANT
派生的 VARIANT
,它允许所有三个在接口之间可互换使用。区别是每个处理 SAFEARRAY
。 MFC框架提供来自 struct tagVARIANT
的 COleSafeArray
,并且包装 SAFEARRAY
library。 ATL框架提供 CComSafeArray
,它不是从 struct tagVARIANT
派生而是使用组合而不是继承。
_variant_t
类有一组构造函数,将接受 VARIANT
或指向 VARIANT
的指针以及用于赋值和转换的操作符方法,它将接受 VARIANT
或指向 VARIANT
。
这些 _variant_t
$ c> VARIANT 使用ATL CComVariant
类和MFC COleVariant
code> COleSafeArray 类,因为这些都派生自 struct tagVARIANT
,它是 VARIANT
。然而,ATL CComSafeArray
模板类不能很好地与 _variant_t
配合使用,因为它不继承自 struct tagVARIANT
。
对于C ++,这意味着一个函数接受 _variant_t
可以与ATL CComVariant
或与MFC COleVariant
和 COleSafeArray
但不能与ATL CComSafeArray
一起使用。这样做将产生编译器错误,例如:
没有适当的用户定义从const ATL :: CComSafeArray 到const _variant_t存在
请参阅用户定义的类型转换(C ++),以获得解释。
对于 CComSafeArray
最简单的工作似乎是定义一个派生自 CComSafeArray
然后提供一个方法,它将提供一个 VARIANT
对象,其中包含 CComSafeArray的
SAFEARRAY
c>
$ b $
CDBsafeArray:public CComSafeArray< VARIANT>
{
int m_size;
HRESULT m_hr;
CDBsafeArray(int nSize = 0):m_size(nSize),m_hr(0)
{
//如果指定大于零的元素数量, $ b //创建将开始为空的SafeArray。
if(nSize> 0)m_hr = this-> Create(nSize);
}
HRESULT CreateOneDim(int nSize)
{
//记住指定的大小并创建SAFEARRAY
m_size = nSize;
m_hr = this-> Create(nSize);
return m_hr;
}
//为那些需要VARIANT而不是CComSafeArray< VARIANT>的
//函数创建SAFEARRAY的VARIANT表示。
//这是提供一个不同格式的副本,不是一个转移
//的所有权。
VARIANT CreateVariant()const {
VARIANT m_variant = {0}; //函数VariantInit()zeros out所以只是做它。
m_variant.vt = VT_ARRAY | VT_VARIANT; //表示我们是一个包含VARIANT的SAFEARRAY
m_variant.parray = this-> m_psa; //提供SAFEARRAY数据结构的地址。
return m_variant; //返回创建的包含SAFEARRAY的VARIANT。
}
};
此类将用于包含字段名称和这些字段的值,ADO _RecordsetPtr 方法
更新()
将被调用如下:
m_hr = m_pRecordSet-> Update(saFields.CreateVariant(),saValues.CreateVariant());
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
?
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();
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
.
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());
这篇关于_variant_t,COleVariant,CComVariant和VARIANT之间的使用差异,并使用SAFEARRAY变体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!