_variant_t、COleVariant、CComVariant 和 VARIANT 以及使用 SAFEARRAY 变体之间的用法差异

Ric*_*ers 5 c++ com atl ado variant

我正在研究几个使用 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."
Run Code Online (Sandbox Code Playgroud)

用HRESULT的0x800a0cc1。

在这两种情况下,我都使用定义为的标准 ADO 记录集对象;

_RecordsetPtr       m_pRecordSet;   // recordset object
Run Code Online (Sandbox Code Playgroud)

在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;
}
Run Code Online (Sandbox Code Playgroud)

这个函数是通过使用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);
Run Code Online (Sandbox Code Playgroud)

因为 Windows 控制台版本没有使用 MFC,所以我CComSafeArray遇到了一些定义问题,这些问题似乎是由于 ATL COM 类是一个模板。

在 MFC 源代码中,COleSafeArray是一个派生自tagVARIANT它的类,它是 aunion的数据结构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
Run Code Online (Sandbox Code Playgroud)

_variant_t似乎是一个包装类VARIANT和似乎没有成为一个转换路径之间CComSafeArray<VARIANT>以及_variant_t然而之间存在转换路径COleSafeArray和_variant_t。

我尝试过的是指定类型m_psa为 a 的类的成员,这会编译但是我在测试应用程序时看到上面的 COM 异常。使用调试器查看对象,指定要更新的字段的对象似乎是正确的。SAFEARRAYVARIANT

所以看起来我混合了不兼容的类。可以使用的SAFEARRAY包装类是_variant_t什么?

Ric*_*ers 8

Microsoft 的简要概述VARIANT和SAFEARRAY

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

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

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

a 的 C/C++ 源代码VARIANT如下所示(所有组件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;
    } ;
Run Code Online (Sandbox Code Playgroud)

COM 使用VARIANTCOM 对象接口中的类型来提供通过接口来回传递数据和执行所需的任何类型的数据转换(编组)的能力。

该VARIANT类型支持多种数据类型,其中之一是SAFEARAY. 因此,您可以使用VARIANTaSAFEARRAY在接口上传递 a 。与其使用显式SAFEARRAY接口,不如指定一个VARIANT接口来识别和处理VARIANT包含SAFEARRAY.

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

VariantInit()
VariantClear()
VariantCopy()
Run Code Online (Sandbox Code Playgroud)

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

SafeArrayCreate()
SafeArrayCreateEx()
SafeArrayCopyData();
Run Code Online (Sandbox Code Playgroud)

三个不同的 MicrosoftVARIANT类:MFC、ATL、Native C++

多年来,Microsoft 提供了几种不同的框架,这些框架和库的目标之一是能够轻松使用 COM 对象。

我们将在以下内容中查看 C++ 的三个不同版本的 VARIANT 类:(1) MFC,(2) ATL,以及 (3) Microsoft 所谓的原生 C++。

MFC 是在 C++ 生命早期开发的复杂框架,为 Windows C++ 程序员提供非常全面的库。

ATL 是一个更简单的框架,旨在帮助人们创建基于 COM 的软件组件。

在_variant_t似乎是一个标准的C ++类包装的VARIANT。

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;
}
Run Code Online (Sandbox Code Playgroud)

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
};
Run Code Online (Sandbox Code Playgroud)

如果我们看一下这些类是我们找到的是的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
};
Run Code Online (Sandbox Code Playgroud)

_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
};
Run Code Online (Sandbox Code Playgroud)

所以我们看到的是三个不同的框架(MFC、ATL 和原生 C++)的工作方式VARIANT和SAFEARRAY.

VARIANT一起使用三个类

这三个都有一个表示 a 的类,VARIANT它是从 派生的,struct tagVARIANT这允许所有三个可以跨接口互换使用。不同之处在于每个人如何处理SAFEARRAY. MFC 框架提供了COleSafeArray衍生自struct tagVARIANT和包装SAFEARRAY库的功能。ATL 框架提供了CComSafeArray哪些不是派生自struct tagVARIANT而是使用组合而不是继承。

的_variant_t类有一组构造函数,其将接受的VARIANT或一个指向VARIANT以及操作员方法分配和转换,将接受一个VARIANT或指针VARIANT。

这些_variant_t方法适用VARIANT于 ATLCComVariant类以及 MFCCOleVariant和COleSafeArray类,因为它们都派生自struct tagVARIANTwhich is VARIANT. 然而,ATLCComSafeArray模板类不能很好地工作,_variant_t因为它不继承自struct tagVARIANT.

对于C ++,这意味着,它利用自变量的函数_variant_t可以与ATL一起使用CComVariant或与MFCCOleVariant和COleSafeArray,但不能与ATL一起使用CComSafeArray。这样做会产生编译器错误,例如:

no suitable user-defined conversion from "const ATL::CComSafeArray<VARIANT, (VARTYPE)12U>" to "const _variant_t" exists
Run Code Online (Sandbox Code Playgroud)

有关说明,请参阅Microsoft Developer Network 文档中的用户定义类型转换 (C++)。

绕了最简单的工作,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.
    }
};
Run Code Online (Sandbox Code Playgroud)

然后将使用此类来包含字段名称和这些字段的值,并且将调用ADO_RecordsetPtr方法,Update()如下所示:

m_hr = m_pRecordSet->Update(saFields.CreateVariant(), saValues.CreateVariant());
Run Code Online (Sandbox Code Playgroud)