我正在调查几个使用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()
时出现以下编译错误:< / p>
no suitable user-defined conversion from "const CDBsafeArray" to "const _variant_t" exists
_variant_t
似乎是VARIANT
的包装类,CComSafeArray<VARIANT>
和_variant_t
之间似乎没有转换路径,但{之间存在转换路径{1}}和COleSafeArray
。
我所尝试的是指定类_variant_t
类m_psa
SAFEARRAY
类型VARIANT
并编译,但是我在测试应用程序时会看到上面的COM异常。使用调试器查看对象时,指定要更新的字段的对象似乎是正确的。
所以看来我混合了不兼容的类。什么是SAFEARRAY
包装器类,它将与_variant_t
一起使用?
答案 0 :(得分:3)
Microsoft VARIANT
和SAFEARRAY
VARIANT
类型用于创建可能包含许多不同类型值的变量。可以在一个点处为这样的变量分配整数值而在另一个点处分配字符串值。 ADO使用VARIANT
和许多不同的方法,以便从数据库读取或写入数据库的值可以通过标准接口提供给调用者,而不是尝试拥有许多不同的,特定于数据类型的接口。
Microsoft指定VARIANT
类型,表示为包含许多字段的C / C ++ struct
。此struct
的两个主要部分是一个字段,其中包含一个值,表示存储在VARIANT
中的当前值的类型以及VARIANT
支持的各种值类型的并集。
除了VARIANT
之外,另一个有用的类型是SAFEARRAY
。 SAFEARRAY
是一个数组,其中包含数组管理数据,有关数组的数据,例如它包含的元素数量,维度以及上限和下限(边界数据允许您拥有任意索引范围)。 / p>
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
。您可以改为指定SAFEARRAY
接口来识别和处理包含VARIANT
的{{1}},而不是使用明确的VARIANT
界面。
提供了几个管理SAFEARRAY
类型的功能,其中一些是:
VARIANT
并且提供了几个用于管理VariantInit()
VariantClear()
VariantCopy()
类型的函数,其中一些是:
SAFEARRAY
三个不同的Microsoft SafeArrayCreate()
SafeArrayCreateEx()
SafeArrayCopyData();
类:MFC,ATL,Native C ++
多年来,Microsoft提供了几种不同的框架,这些框架和库的目标之一就是能够轻松使用COM对象。
我们将在下面讨论C ++的三种不同版本的VARIANT类:(1)MFC,(2)ATL,以及(3)Microsoft称之为本机C ++的内容。
MFC是一个在C ++生命早期开发的复杂框架,为Windows C ++程序员提供了一个非常全面的库。
ATL是一个更简单的框架,用于帮助人们创建基于COM的软件组件。
VARIANT
似乎是_variant_t
的标准C ++类包装器。
ADO VARIANT
类使用_RecordsetPtr
方法接受Update()
对象,如下所示:
_variant_t
MFC提供了一组用于处理COM对象的类,其中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;
}
类的类为VARIANT
和COleVariant
。如果我们查看这两个类的声明,我们会看到以下内容:
COleSafeArray
如果我们查看这些类的ATL版本,我们发现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
};
和CComVariant
,CComSafeArray
是一个C ++模板。使用CComSafeArray
声明变量时,指定要包含在基础CComSafeArray
结构中的值的类型。声明如下:
SAFEARRAY
_variant_t类声明如下:
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
};
因此,我们看到三个不同框架(MFC,ATL和本机C ++)如何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
};
和VARIANT
之间存在细微差别。
一起使用三个SAFEARRAY
课程
这三个都有一个类来表示VARIANT
,它来自VARIANT
,它允许所有三个在接口之间互换使用。 区别在于每个处理struct tagVARIANT
的方式。 MFC框架提供SAFEARRAY
,它派生自COleSafeArray
并包装struct tagVARIANT
库。 ATL框架提供SAFEARRAY
,它不是从CComSafeArray
派生而是使用组合而不是继承。
struct tagVARIANT
类有一组构造函数,它们将接受_variant_t
或指向VARIANT
的指针以及用于赋值和转换的运算符方法,它们将接受{{1} }或指向VARIANT
的指针。
VARIANT
的这些VARIANT
方法与ATL _variant_t
类以及MFC VARIANT
和CComVariant
类一起使用,因为它们都来自{COleVariant
1}}这是COleSafeArray
。但是,ATL struct tagVARIANT
模板类与VARIANT
不兼容,因为它不会从CComSafeArray
继承。
对于C ++,这意味着带有_variant_t
参数的函数可以与ATL struct tagVARIANT
或MFC _variant_t
和CComVariant
一起使用,但不能与ATL COleVariant
一起使用。这样做会产生编译器错误,例如:
COleSafeArray
有关说明,请参阅Microsoft Developer Network文档中的User-Defined Type Conversions (C++)。
CComSafeArray
的最简单的解决方法似乎是定义一个派生自no suitable user-defined conversion from "const ATL::CComSafeArray<VARIANT, (VARTYPE)12U>" to "const _variant_t" exists
的类,然后提供一个方法,该方法将提供一个CComSafeArray
对象包装CComSafeArray
1}} VARIANT
内SAFEARRAY
的对象。
CComSafeArray
然后,此类将用于包含字段名称和这些字段的值,VARIANT
的ADO 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.
}
};
方法将被称为:
_RecordsetPtr