【问题标题】:usage differences between _variant_t, COleVariant, CComVariant, and VARIANT and using SAFEARRAY variations_variant_t、COleVariant、CComVariant 和 VARIANT 和使用 SAFEARRAY 变体之间的用法差异
【发布时间】:2017-01-11 13:22:10
【问题描述】:

我正在研究几个使用 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."

具有HRESULT0x800a0cc1

在这两种情况下,我都使用定义为的标准 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 的类,unionVARIANT 的数据结构。但是在 ATL COM 中,CComSafeArray 是一个模板,我将其用作 CComSafeArray<VARIANT>,这似乎是合理的。

但是,当我尝试使用使用此模板定义的变量时,即派生自 CComSafeArray<VARIANT> 的类 CDBsafeArray,我在调用 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 成员,该成员是VARIANT 类型的SAFEARRAY,并且可以编译但是我在测试应用程序时看到了上面的COM 异常。使用调试器查看对象,指定要更新的字段的对象似乎是正确的。

看来我正在混合不兼容的类。与_variant_t 一起使用的SAFEARRAY 包装类是什么?

【问题讨论】:

  • 这样的东西应该可以工作:_variant_t var; var.parray = myarray.Detach(); var.vt = VT_ARRAY | VT_VARIANT;。其中myarrayCComSafeArray<VARIANT>。看起来没有与_variant_t 自然互操作的安全数组包装器,因此您需要下拉到原始VARIANT 级别。
  • @IgorTandetnik,感谢您的帮助。我会看看这条路,看看它通向何方。我想知道我是否需要做类似的事情。在使用 MFC 示例时,我有自己的 VARIANT 包装器,直到我意识到 MFC 有一些类比我的工作更好,并且希望更安全。
  • @IgorTandetnik,我正在研究的是使CDBsafeArray 类派生自tagVARIANT,然后使用CComSafeArray<VARIANT>CDBsafeArray 类中创建一个成员m_SafeArray。然后我修改CDBsafeArray 类的tagVARIANT 部分,使其看起来像一个带有vt = VT_ARRAY | VT_VARIANT;parray = m_SafeArray.m_psa; 的VARIANT 数组,这导致CDBsafeArray 对象看起来像一个SAFEARRAY 的VARIANTS。这在我的有限示例中运行良好,但是我怀疑会出现比我正在做的更复杂的问题。
  • @IgorTandetnik 我在想我真正需要的是有一个转换运算符,它会做一个惰性求值,并在调用转换运算符时使用CComSafeArray<VARIANT> 数据创建tagVARIANT 表示.这样,如果CComSafeArray<VARIANT> 成员发生变化,转换操作员只会显示最新版本。
  • @IgorTandetnik 我已经发布了答案。您能否查看一下,如果有任何严重错误,请告诉我?

标签: c++ com ado atl variant


【解决方案1】:

微软VARIANTSAFEARRAY的简要概述

VARIANT 类型用于创建一个变量,该变量可能包含许多不同类型的值。这样的变量可以在某一点被分配一个整数值,而在另一点被分配一个字符串值。 ADO 使用VARIANT 和许多不同的方法,以便可以通过标准接口将从数据库读取或写入数据库的值提供给调用方,而不是尝试使用许多不同的、特定于数据类型的接口。

Microsoft 指定 VARIANT 类型,它表示为 C/C++ struct,其中包含许多字段。这个struct 的两个主要部分是一个字段,其中包含一个值,该值表示存储在VARIANT 中的当前值的类型,以及VARIANT 支持的各种值类型的联合。

除了VARIANT,另一个有用的类型是SAFEARRAYSAFEARRAY 是一个数组,其中包含数组管理数据、有关数组的数据,例如它包含多少元素、它的维度以及上限和下限(边界数据允许您拥有任意索引范围)。

VARIANT 的 C/C++ 源代码如下所示(所有组件 structunion 成员似乎都是匿名的,例如 __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 接口来识别和处理包含SAFEARRAYVARIANT,而不是使用显式的SAFEARRAY 接口。

提供了几个函数来管理VARIANT 类型,其中一些是:

VariantInit()
VariantClear()
VariantCopy()

并且提供了几个函数来管理SAFEARRAY 类型,其中一些是:

SafeArrayCreate()
SafeArrayCreateEx()
SafeArrayCopyData();

三种不同的 Microsoft 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 类型的类为 COleVariantCOleSafeArray。如果我们查看这两个类的声明,我们会看到以下内容:

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 版本,我们会发现 CComVariantCComSafeArray 但是 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++)在 VARIANTSAFEARRAY 的执行方式之间存在细微差别。

一起使用三个 VARIANT

这三个都有一个代表VARIANT 的类,它派生自struct tagVARIANT,这使得所有三个都可以跨接口互换使用。 不同之处在于每个处理SAFEARRAY 的方式。 MFC 框架提供COleSafeArray,它派生自struct tagVARIANT 并包装SAFEARRAY 库。 ATL 框架提供了CComSafeArray,它不是从struct tagVARIANT 派生的,而是使用组合而不是继承。

_variant_t 类具有一组构造函数,它们将接受 VARIANT 或指向 VARIANT 的指针,以及用于赋值和转换的运算符方法将接受 VARIANT 或指向 @987654388 的指针@。

VARIANT 的这些 _variant_t 方法可与 ATL CComVariant 类以及 MFC COleVariantCOleSafeArray 类一起使用,因为它们都派生自 struct tagVARIANT,即 VARIANT。但是 ATL CComSafeArray 模板类不能很好地与 _variant_t 一起使用,因为它不是从 struct tagVARIANT 继承的。

对于 C++,这意味着带有 _variant_t 参数的函数可以与 ATL CComVariant 或 MFC COleVariantCOleSafeArray 一起使用,但不能与 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 的类,然后提供一个方法来提供一个包装@987654409 的SAFEARRAY 对象的VARIANT 对象@ 在 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.
    }
};

然后,此类将用于包含字段名称和这些字段的值,并且 Update() 的 ADO _RecordsetPtr 方法将被称为:

m_hr = m_pRecordSet->Update(saFields.CreateVariant(), saValues.CreateVariant());

【讨论】:

猜你喜欢
  • 2010-12-19
  • 2010-10-12
  • 2011-01-18
  • 2015-08-26
  • 2011-11-07
  • 2019-12-08
  • 2022-01-20
  • 1970-01-01
  • 2016-08-07
相关资源
最近更新 更多