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

问题描述 投票:2回答:1

我正在研究几个使用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的类,它是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一起使用?

c++ com ado atl variant
1个回答
3
投票

Microsoft 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。您可以改为指定一个SAFEARRAY接口来识别和处理包含VARIANTVARIANT,而不是使用明确的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或指向VARIANT的指针。

这些用于_variant_tVARIANT方法与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的类,然后提供一个方法,提供一个VARIANT对象,包裹SAFEARRAY CComSafeArrayVARIANT对象。

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的ADO Update()方法将被调用为:

m_hr = m_pRecordSet->Update(saFields.CreateVariant(), saValues.CreateVariant());
© www.soinside.com 2019 - 2024. All rights reserved.