_variant_t、COleVariant、CComVariant 和 VARIANT 之间的使用差异以及使用 SAFEARRAY 变体
usage differences between _variant_t, COleVariant, CComVariant, and VARIANT and using SAFEARRAY variations
我正在调查几个 Visual Studio 2015 C++ 项目类型,它们使用 ADO 访问 SQL 服务器数据库。这个简单的示例针对 table 执行 select,读入行,更新每一行,并更新 table.
MFC 版本工作正常。 Windows 控制台版本是我在更新记录集中的行时遇到问题的地方。记录集的 update()
方法抛出 COM 异常,错误文本为:
L"Item cannot be found in the collection corresponding to the requested name or ordinal."
HRESULT
为 0x800a0cc1
。
在这两种情况下,我都使用定义为的标准 ADO 记录集对象;
_RecordsetPtr m_pRecordSet; // recordset object
在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\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;
}
此函数是通过使用两个 COleSafeArray
对象组合成一个帮助程序 class 来调用的,以便更容易指定要更新的列名和值。
// 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);
因为 Windows 控制台版本没有使用 MFC,我 运行 遇到了一些定义问题,这些问题似乎是由于 ATL COM class CComSafeArray
是模板。
在 MFC 源中,COleSafeArray
是从 tagVARIANT
派生的 class,后者是 union
,是 VARIANT
的数据结构。但是在 ATL COM 中,CComSafeArray
是我用作 CComSafeArray<VARIANT>
的模板,这似乎是合理的。
然而,当我尝试使用此模板定义的变量时,class CDBsafeArray
派生自 CComSafeArray<VARIANT>
,我在调用 m_pRecordSet->Update()
:
no suitable user-defined conversion from "const CDBsafeArray" to "const _variant_t" exists
_variant_t
似乎是 VARIANT
的包装器 class 并且在 CComSafeArray<VARIANT>
和 _variant_t
之间似乎没有转换路径但是那里是COleSafeArray
和_variant_t
之间的转换路径。
我尝试的是指定 class 的 m_psa
成员,它是 VARIANT
类型的 SAFEARRAY
并且这会编译但是我看到了 COM 异常上面的测试应用程序时。使用调试器查看对象,指定要更新的字段的对象似乎是正确的。
看来我正在混合不兼容的 classes。什么是可以与 _variant_t
一起使用的 SAFEARRAY
包装器 class?
Microsoft VARIANT
和 SAFEARRAY
简要概述
VARIANT
类型用于创建可能包含许多不同类型的值的变量。这样的变量可以在一处被分配一个整数值而在另一处被分配一个字符串值。 ADO 将 VARIANT
与许多不同的方法结合使用,以便从数据库读取或写入数据库的值可以通过标准接口提供给调用者,而不是尝试使用许多不同的、数据类型特定的接口。
Microsoft 指定 VARIANT
类型,表示为 C/C++ 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 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 在 COM 对象接口中使用 VARIANT
类型来提供通过接口来回传递数据并执行所需的任何类型的数据转换(封送处理)的能力。
VARIANT
类型支持多种数据类型,SAFEARAY
就是其中之一。因此,您可以使用 VARIANT
通过接口传递 SAFEARRAY
。您可以指定一个 VARIANT
接口来识别和处理包含 SAFEARRAY
.
的 VARIANT
,而不是使用显式 SAFEARRAY
接口
提供了几个函数来管理 VARIANT
类型,其中一些是:
VariantInit()
VariantClear()
VariantCopy()
并且提供了几个函数来管理 SAFEARRAY
类型,其中一些是:
SafeArrayCreate()
SafeArrayCreateEx()
SafeArrayCopyData();
三个不同的 Microsoft VARIANT
classes: MFC, ATL, Native C++
多年来,Microsoft 提供了几种不同的框架,这些框架和库的目标之一是能够轻松地使用 COM 对象。
我们将在下面查看适用于 C++ 的 VARIANT classes 的三个不同版本:(1) MFC,(2) ATL,以及 (3) Microsoft 所谓的本机 C++。
MFC 是 C++ 早期开发的复杂框架,为 Windows C++ 程序员提供了一个非常全面的库。
ATL 是一个更简单的框架,旨在帮助人们创建基于 COM 的软件组件。
_variant_t
似乎是 VARIANT
的标准 C++ class 包装器。
ADO _RecordsetPtr
class 有 Update()
方法接受一个 _variant_t
对象,它看起来像:
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 提供了一组 classes 用于处理 COM 对象,classes 用于 VARIANT
类型 COleVariant
和 COleSafeArray
。如果我们查看这两个 classes 的声明,我们会看到以下内容:
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
};
如果我们查看这些 class 的 ATL 版本,我们会发现 CComVariant
和 CComSafeArray
然而 CComSafeArray
是一个 C++ 模板。当您使用 CComSafeArray
声明变量时,您指定要包含在基础 SAFEARRAY
结构中的值的类型。声明如下所示:
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
};
_variant_t class声明如下:
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
};
所以我们看到的是三种不同框架(MFC、ATL 和本机 C++)在执行 VARIANT
和 SAFEARRAY
时的细微差别。
三个一起使用VARIANT
类
所有这三个都有一个 class 来表示 VARIANT
,它派生自 struct tagVARIANT
,这允许所有三个都可以跨接口互换使用。 不同之处在于它们如何处理 SAFEARRAY
。 MFC 框架提供了 COleSafeArray
,它派生自 struct tagVARIANT
并包装了 SAFEARRAY
库。 ATL 框架提供 CComSafeArray
,它不是从 struct tagVARIANT
派生的,而是使用组合而不是继承。
_variant_t
class 有一组构造函数可以接受 VARIANT
或指向 VARIANT
的指针以及用于赋值和转换的运算符方法将接受 VARIANT
或指向 VARIANT
.
的指针
VARIANT
的这些 _variant_t
方法适用于 ATL CComVariant
class 以及 MFC COleVariant
和 COleSafeArray
class 是因为这些都是从 struct tagVARIANT
派生的,即 VARIANT
。但是 ATL CComSafeArray
模板 class 不能很好地与 _variant_t
一起使用,因为它没有继承自 struct tagVARIANT
.
对于 C++,这意味着接受参数 _variant_t
的函数可以与 ATL CComVariant
或 MFC COleVariant
和 COleSafeArray
一起使用,但可以不能与 ATL CComSafeArray
一起使用。这样做会产生编译器错误,例如:
no suitable user-defined conversion from "const ATL::CComSafeArray<VARIANT, (VARTYPE)12U>" to "const _variant_t" exists
有关解释,请参阅 Microsoft Developer Network 文档中的 User-Defined Type Conversions (C++)。
CComSafeArray
的最简单解决方法似乎是定义一个从 CComSafeArray
派生的 class,然后提供一个方法来提供 VARIANT
将 CComSafeArray
的 SAFEARRAY
对象包装在 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.
}
};
此 class 将用于包含字段名称和这些字段的值,并且 Update()
的 ADO _RecordsetPtr
方法将被调用为:
m_hr = m_pRecordSet->Update(saFields.CreateVariant(), saValues.CreateVariant());
我正在调查几个 Visual Studio 2015 C++ 项目类型,它们使用 ADO 访问 SQL 服务器数据库。这个简单的示例针对 table 执行 select,读入行,更新每一行,并更新 table.
MFC 版本工作正常。 Windows 控制台版本是我在更新记录集中的行时遇到问题的地方。记录集的 update()
方法抛出 COM 异常,错误文本为:
L"Item cannot be found in the collection corresponding to the requested name or ordinal."
HRESULT
为 0x800a0cc1
。
在这两种情况下,我都使用定义为的标准 ADO 记录集对象;
_RecordsetPtr m_pRecordSet; // recordset object
在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\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;
}
此函数是通过使用两个 COleSafeArray
对象组合成一个帮助程序 class 来调用的,以便更容易指定要更新的列名和值。
// 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);
因为 Windows 控制台版本没有使用 MFC,我 运行 遇到了一些定义问题,这些问题似乎是由于 ATL COM class CComSafeArray
是模板。
在 MFC 源中,COleSafeArray
是从 tagVARIANT
派生的 class,后者是 union
,是 VARIANT
的数据结构。但是在 ATL COM 中,CComSafeArray
是我用作 CComSafeArray<VARIANT>
的模板,这似乎是合理的。
然而,当我尝试使用此模板定义的变量时,class CDBsafeArray
派生自 CComSafeArray<VARIANT>
,我在调用 m_pRecordSet->Update()
:
no suitable user-defined conversion from "const CDBsafeArray" to "const _variant_t" exists
_variant_t
似乎是 VARIANT
的包装器 class 并且在 CComSafeArray<VARIANT>
和 _variant_t
之间似乎没有转换路径但是那里是COleSafeArray
和_variant_t
之间的转换路径。
我尝试的是指定 class 的 m_psa
成员,它是 VARIANT
类型的 SAFEARRAY
并且这会编译但是我看到了 COM 异常上面的测试应用程序时。使用调试器查看对象,指定要更新的字段的对象似乎是正确的。
看来我正在混合不兼容的 classes。什么是可以与 _variant_t
一起使用的 SAFEARRAY
包装器 class?
Microsoft VARIANT
和 SAFEARRAY
VARIANT
类型用于创建可能包含许多不同类型的值的变量。这样的变量可以在一处被分配一个整数值而在另一处被分配一个字符串值。 ADO 将 VARIANT
与许多不同的方法结合使用,以便从数据库读取或写入数据库的值可以通过标准接口提供给调用者,而不是尝试使用许多不同的、数据类型特定的接口。
Microsoft 指定 VARIANT
类型,表示为 C/C++ 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 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 在 COM 对象接口中使用 VARIANT
类型来提供通过接口来回传递数据并执行所需的任何类型的数据转换(封送处理)的能力。
VARIANT
类型支持多种数据类型,SAFEARAY
就是其中之一。因此,您可以使用 VARIANT
通过接口传递 SAFEARRAY
。您可以指定一个 VARIANT
接口来识别和处理包含 SAFEARRAY
.
VARIANT
,而不是使用显式 SAFEARRAY
接口
提供了几个函数来管理 VARIANT
类型,其中一些是:
VariantInit()
VariantClear()
VariantCopy()
并且提供了几个函数来管理 SAFEARRAY
类型,其中一些是:
SafeArrayCreate()
SafeArrayCreateEx()
SafeArrayCopyData();
三个不同的 Microsoft VARIANT
classes: MFC, ATL, Native C++
多年来,Microsoft 提供了几种不同的框架,这些框架和库的目标之一是能够轻松地使用 COM 对象。
我们将在下面查看适用于 C++ 的 VARIANT classes 的三个不同版本:(1) MFC,(2) ATL,以及 (3) Microsoft 所谓的本机 C++。
MFC 是 C++ 早期开发的复杂框架,为 Windows C++ 程序员提供了一个非常全面的库。
ATL 是一个更简单的框架,旨在帮助人们创建基于 COM 的软件组件。
_variant_t
似乎是 VARIANT
的标准 C++ class 包装器。
ADO _RecordsetPtr
class 有 Update()
方法接受一个 _variant_t
对象,它看起来像:
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 提供了一组 classes 用于处理 COM 对象,classes 用于 VARIANT
类型 COleVariant
和 COleSafeArray
。如果我们查看这两个 classes 的声明,我们会看到以下内容:
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
};
如果我们查看这些 class 的 ATL 版本,我们会发现 CComVariant
和 CComSafeArray
然而 CComSafeArray
是一个 C++ 模板。当您使用 CComSafeArray
声明变量时,您指定要包含在基础 SAFEARRAY
结构中的值的类型。声明如下所示:
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
};
_variant_t class声明如下:
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
};
所以我们看到的是三种不同框架(MFC、ATL 和本机 C++)在执行 VARIANT
和 SAFEARRAY
时的细微差别。
三个一起使用VARIANT
类
所有这三个都有一个 class 来表示 VARIANT
,它派生自 struct tagVARIANT
,这允许所有三个都可以跨接口互换使用。 不同之处在于它们如何处理 SAFEARRAY
。 MFC 框架提供了 COleSafeArray
,它派生自 struct tagVARIANT
并包装了 SAFEARRAY
库。 ATL 框架提供 CComSafeArray
,它不是从 struct tagVARIANT
派生的,而是使用组合而不是继承。
_variant_t
class 有一组构造函数可以接受 VARIANT
或指向 VARIANT
的指针以及用于赋值和转换的运算符方法将接受 VARIANT
或指向 VARIANT
.
VARIANT
的这些 _variant_t
方法适用于 ATL CComVariant
class 以及 MFC COleVariant
和 COleSafeArray
class 是因为这些都是从 struct tagVARIANT
派生的,即 VARIANT
。但是 ATL CComSafeArray
模板 class 不能很好地与 _variant_t
一起使用,因为它没有继承自 struct tagVARIANT
.
对于 C++,这意味着接受参数 _variant_t
的函数可以与 ATL CComVariant
或 MFC COleVariant
和 COleSafeArray
一起使用,但可以不能与 ATL CComSafeArray
一起使用。这样做会产生编译器错误,例如:
no suitable user-defined conversion from "const ATL::CComSafeArray<VARIANT, (VARTYPE)12U>" to "const _variant_t" exists
有关解释,请参阅 Microsoft Developer Network 文档中的 User-Defined Type Conversions (C++)。
CComSafeArray
的最简单解决方法似乎是定义一个从 CComSafeArray
派生的 class,然后提供一个方法来提供 VARIANT
将 CComSafeArray
的 SAFEARRAY
对象包装在 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.
}
};
此 class 将用于包含字段名称和这些字段的值,并且 Update()
的 ADO _RecordsetPtr
方法将被调用为:
m_hr = m_pRecordSet->Update(saFields.CreateVariant(), saValues.CreateVariant());