_variant_t,COleVariant,CComVariant和VARIANT之间的使用差异,并使用SAFEARRAY变体 [英] usage differences between _variant_t, COleVariant, CComVariant, and VARIANT and using SAFEARRAY variations

查看:306
本文介绍了_variant_t,COleVariant,CComVariant和VARIANT之间的使用差异,并使用SAFEARRAY变体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在调查几个使用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指定 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 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆