我正在研究几个使用 ADO 访问 SQL Server 数据库的 Visual Studio 2015 C++ 项目类型。简单示例对表执行选择、读入行、更新每一行并更新表。
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
调用此函数对象组合成一个帮助器类,以便更容易地指定要更新的列名和值。// 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 类
CComSafeArray
作为模板。在 MFC 源中,
COleSafeArray
是从 tagVARIANT
派生的类这是一个 union
这是 VARIANT
的数据结构.但是在 ATL COM 中,CComSafeArray
是我用作 CComSafeArray<VARIANT>
的模板这似乎是合理的。但是,当我尝试使用此模板定义的变量时,类
CDBsafeArray
源自 CComSafeArray<VARIANT>
,我在调用 m_pRecordSet->Update()
时收到以下编译错误:no suitable user-defined conversion from "const CDBsafeArray" to "const _variant_t" exists
_variant_t
似乎是 VARIANT
的包装类并且在 CComSafeArray<VARIANT>
之间似乎没有转换路径和 _variant_t
但是在 COleSafeArray
之间有一个转换路径和 _variant_t
.我尝试过的是指定
m_psa
SAFEARRAY
类的成员类型 VARIANT
这可以编译,但是我在测试应用程序时看到了上面的 COM 异常。使用调试器查看对象,指定要更新的字段的对象似乎是正确的。所以看起来我混合了不兼容的类。什么是
SAFEARRAY
将与 _variant_t
一起使用的包装类?
最佳答案
Microsoft 简要概述 VARIANT
和 SAFEARRAY
VARIANT
type 用于创建一个变量,该变量可能包含许多不同类型的值。这样的变量可以在一个点被分配一个整数值,在另一个点被分配一个字符串值。 ADO 使用 VARIANT
使用许多不同的方法,以便从数据库读取或写入数据库的值可以通过标准接口(interface)提供给调用者,而不是试图拥有许多不同的、特定于数据类型的接口(interface)。
Microsoft 指定了 VARIANT
表示为 C/C++ 的类型 struct
其中包含许多字段。此 struct
的两个主要部分是一个字段,其中包含一个值,该值表示存储在 VARIANT
中的当前值的类型。以及 VARIANT
支持的各种值类型的 union .
除了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 使用
VARIANT
键入 COM 对象接口(interface)以提供通过接口(interface)来回传递数据和执行所需的任何类型的数据转换(编码)的能力。VARIANT
type 支持多种数据类型,其中之一是 SAFEARAY
.所以你可以使用 VARIANT
通过 SAFEARRAY
通过接口(interface)。而不是明确的 SAFEARRAY
接口(interface),您可以改为指定 VARIANT
将识别和处理 VARIANT
的接口(interface)包含 SAFEARRAY
.提供了几个功能来管理
VARIANT
键入其中一些是:VariantInit()
VariantClear()
VariantCopy()
并且提供了几个功能来管理
SAFEARRAY
键入其中一些是:SafeArrayCreate()
SafeArrayCreateEx()
SafeArrayCopyData();
三个不同的微软
VARIANT
类:MFC、ATL、 native C++ 多年来,Microsoft 提供了几种不同的框架,这些框架和库的目标之一是能够轻松使用 COM 对象。
我们将在以下内容中查看 C++ 的三个不同版本的 VARIANT 类:(1) MFC,(2) ATL,以及 (3) Microsoft 所谓的原生 C++。
MFC 是在 C++ 生命早期开发的复杂框架,为 Windows C++ 程序员提供非常全面的库。
ATL 是一个更简单的框架,旨在帮助人们创建基于 COM 的软件组件。
_variant_t
似乎是 VARIANT
的标准 C++ 类包装器.ADO
_RecordsetPtr
类有 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 提供了一组用于处理 COM 对象的类,其中包含
VARIANT
的类。类型是 COleVariant
和 COleSafeArray
.如果我们查看这两个类的声明,我们会看到以下内容: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
};
如果我们查看这些类的 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 _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
一起上课所有三个都有一个类来表示
VARIANT
源自 struct tagVARIANT
这允许所有三个可以跨接口(interface)互换使用。不同之处在于每个人如何处理 SAFEARRAY
. MFC 框架提供 COleSafeArray
源自 struct tagVARIANT
并包裹 SAFEARRAY
图书馆。 ATL 框架提供 CComSafeArray
不是源自 struct tagVARIANT
而是使用组合而不是继承。_variant_t
类有一组构造函数,它们将接受 VARIANT
或指向 VARIANT
的指针以及接受 VARIANT
的赋值和转换操作符方法或指向 VARIANT
的指针.这些
_variant_t
VARIANT
的方法与 ATL 合作 CComVariant
类和 MFC COleVariant
和 COleSafeArray
类,因为这些都派生自 struct tagVARIANT
这是 VARIANT
.然而 ATL CComSafeArray
模板类不适用于 _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
见 User-Defined Type Conversions (C++) Microsoft Developer Network 文档中的说明。
CComSafeArray
的最简单解决方法似乎是定义一个派生自 CComSafeArray
的类然后提供一个方法来提供 VARIANT
包装 SAFEARRAY
的对象CComSafeArray
的对象内部 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.
}
};
然后,此类将用于包含字段名称和这些字段的值以及 ADO
_RecordsetPtr
Update()
的方法将被称为:m_hr = m_pRecordSet->Update(saFields.CreateVariant(), saValues.CreateVariant());
关于c++ - _variant_t、COleVariant、CComVariant 和 VARIANT 以及使用 SAFEARRAY 变体之间的用法差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39309122/