6
\$\begingroup\$

Here is a C++ wrapper for SAFEARRAY, including the ability to expose it as a VARIANT, and to create one out of VARIANT.

I am aware of CComSafeArray, however i don't use VC++ so I do not have licence to use this; and also, that wrapper takes the T as a template parameter to the class, however mine takes it as template parameter to the creation function and element access function instead, so my wrapper can do things such as extract from a Variant without knowing what sort of element is in the array yet.

The purpose of parray_variant is to use directly as the argument to a function that takes VARIANT as parameter type (so hopefully there is no need to copy the data, since no VariantClear occurs).

Known bugs

  • Array of VT_BOOL doesn't work, as my template type selector can't distinguish that from VT_I2 - is there any solution to this?

The code:

#ifndef H_MYSAFEARRAY
#define H_MYSAFEARRAY

#include <vector>

namespace My
{
    template<typename T> VARENUM VT_OF();
    template<typename T> VARENUM VT_OF_OBJ(T const &) { return VT_OF<T>(); }    // deducible version
    #define VT_MAP(T, V) template<> inline VARENUM VT_OF<T>() { return V; }
    VT_MAP(char, VT_I1);
    VT_MAP(unsigned char, VT_UI1);
    VT_MAP(short, VT_I2);
    VT_MAP(unsigned short, VT_UI2);
    VT_MAP(long, VT_I4);
    VT_MAP(unsigned long, VT_UI4);
    VT_MAP(int, VT_I4);
    VT_MAP(unsigned int, VT_UI4);
    VT_MAP(float, VT_R4);
    VT_MAP(double, VT_R8);
//    VT_MAP(VARIANT_BOOL, VT_BOOL);
//    VT_MAP(SCODE, VT_ERROR);
    VT_MAP(CY, VT_CY);
//    VT_MAP(DATE, VT_DATE);
    VT_MAP(BSTR, VT_BSTR);
    VT_MAP(IUnknown *, VT_UNKNOWN);
    VT_MAP(IDispatch *, VT_DISPATCH);

    #undef VT_MAP

// Make a safearray and shallow-copy the items into it.
// Note, if you are copying in BSTRs then the array will now own those strings.

    template<typename InIt>
    LPSAFEARRAY make_safearray(InIt begin, InIt end)
    {
        SAFEARRAYBOUND bound;
        bound.lLbound = 0;
        bound.cElements = std::distance(begin, end);

        LPSAFEARRAY psa = SafeArrayCreate(VT_OF_OBJ(*begin), 1, &bound);
        if (!psa)
            throw std::runtime_error("Unable to create SafeArray");

        if (bound.cElements)
            std::copy(begin, end, static_cast< typename std::iterator_traits<InIt>::value_type *>(psa->pvData) );
        return psa;
    }
//
// Copy the items from a SAFEARRAY into a vector
//
    template<typename T>
    std::vector<T> read_safearray(LPSAFEARRAY psa)
    {
        if (!psa || SafeArrayGetElemsize(psa) != sizeof(T))
            throw std::runtime_error("SafeArray has wrong size elements");
        T const *ptr = static_cast<T const *>(psa->pvData);
        size_t n = psa->rgsabound[0].cElements;
        return std::vector<T>(ptr, ptr + n);
    }
//
// Extract a SAFEARRAY from a VARIANT. returns 0 if array of wrong type or not present
// Note: VariantChangeType doesn't work on arrays. The array must be the right type initially.
//
    inline LPSAFEARRAY get_parray_nocheck(VARIANT const &var)
    {
    // Check it is actually an array
        if ( !(V_VT(&var) & VT_ARRAY) )
            return 0;

    // Extract the array (differs if it came in by reference)
        if ( V_VT(&var) & VT_BYREF )
            return *V_ARRAYREF(&var);
        else
            return V_ARRAY(&var);
    }

    template<typename T>
    LPSAFEARRAY get_parray(VARIANT const &var)
    {
    // Use the template-parameter as a check that the array contains the right type.
        if ( VT_OF<T>() != (V_VT(&var) & VT_TYPEMASK) )
            return 0;

    // Call the non-checking function to extract the pointer
        return get_parray_nocheck(var);
    }

//
// Create a VARIANT whose value is a SAFEARRAY (Does not free anything)
// Note, take care to not double-free the array; any variant functions
// such as VariantClear will free the array.
//
    inline VARIANT parray_variant(LPSAFEARRAY psa)
    {
        VARIANT var;
        VARTYPE vt;
        VariantInit(&var);

        if ( psa &&  S_OK == SafeArrayGetVartype(psa, &vt) )
        {
            V_ARRAY(&var) = psa;
            V_VT(&var) = VT_ARRAY | vt;
        }

        return var;
    }

    struct RaiiSafeArray
    {
        RaiiSafeArray(): psa(0) {}
        RaiiSafeArray(LPSAFEARRAY p): psa(p) {}
        ~RaiiSafeArray() { if (psa) SafeArrayDestroy(psa); }

        template<typename InIt> RaiiSafeArray(InIt begin, InIt end)
            : psa( make_safearray(begin, end) ) {}

        void attach(LPSAFEARRAY p) { if (psa) SafeArrayDestroy(psa); psa = p; }
        LPSAFEARRAY detach() { LPSAFEARRAY p = psa; psa = 0; return p; }

        LPSAFEARRAY get() const { return psa; }
        LPSAFEARRAY *operator&() { return &psa; }
        bool operator!() { return psa == 0; }
        template<typename T> std::vector<T> get_vector() const { return read_safearray<T>(psa); }

    // Same reservations apply to this function as to parray_variant() - use wisely
        VARIANT get_variant() const { return parray_variant(psa); }

    private:
        LPSAFEARRAY psa;

    // prevent accidental copying
        RaiiSafeArray(RaiiSafeArray const &);
        void operator=(RaiiSafeArray const &);
    };


}

#endif
\$\endgroup\$
2
  • \$\begingroup\$ What is SAFEARRAY \$\endgroup\$ Commented Jul 29, 2014 at 22:36
  • 1
    \$\begingroup\$ @LokiAstari it is the Automation-compatible array type, see here \$\endgroup\$
    – M.M
    Commented Jul 29, 2014 at 22:41

1 Answer 1

1
\$\begingroup\$

Array of VT_BOOL doesn't work, as my template type selector can't distinguish that from VT_I2 - is there any solution to this?

Underneath the hood, a VARIANT_BOOL is just a short, so your map strategy won't work. Rather than using your mapping scheme, I would do something like this:

template <class T>
struct VTValue ;

template <>
struct VTValue <char>
{
    const static VARENUM value = VT_I1 ;
};

template <>
struct VTValue <short>
{
    const static VARENUM value = VT_I2 ;
};

// And so on, don't worry about about having one for VT_BOOL.

Then I would change your make_safearray() function to something like this*:

template<typename InIt>
LPSAFEARRAY make_safearray(InIt begin, InIt end, VARENUM varenum = VTValue <typename std::iterator_traits<InIt>::value_type>::value)
{ 
    SAFEARRAYBOUND bound;
    bound.lLbound = 0;
    bound.cElements = std::distance(begin, end);

    LPSAFEARRAY psa = SafeArrayCreate(varenum, 1, &bound);
    if (!psa)
        throw std::runtime_error("Unable to create SafeArray");

    if (bound.cElements)
        std::copy(begin, end, stdext::make_checked_array_iterator (static_cast< typename std::iterator_traits<InIt>::value_type *>(psa->pvData), bound.cElements) );
    return psa;
}

*You can ignore the stdext::make_checked_array_iterator() stuff and just leave what you had. I needed that to compile your program without warnings on Visual Studio.

Now you could do something like this:

#include "SafeArrayWrapper.h"

int main ()
{
    const short arr [] = { 5, 10, 15, 20 } ;

    LPSAFEARRAY psa = My::make_safearray (arr, arr + 4) ;

    std::vector <short> v = My::read_safearray <short> (psa) ;

    if (psa) {
        ::SafeArrayDestroy (psa) ;
    }

    const VARIANT_BOOL arr2 [] = { VARIANT_TRUE, VARIANT_TRUE, VARIANT_FALSE, VARIANT_FALSE } ;

    LPSAFEARRAY psa2 = My::make_safearray (arr2, arr2 + 4, VT_BOOL) ;

    std::vector <VARIANT_BOOL> v2 = My::read_safearray <VARIANT_BOOL> (psa2) ;

    if (psa2) {
        ::SafeArrayDestroy (psa) ;
    }

    return 0 ;
}

You could also make your read_safearray() more generic by adding an overload:

template<typename T, typename OutIt>
OutIt read_safearray(LPSAFEARRAY psa, OutIt out)
{
    if (!psa || SafeArrayGetElemsize(psa) != sizeof(T))
        throw std::runtime_error("SafeArray has wrong size elements");
    T const *ptr = static_cast<T const *>(psa->pvData);
    size_t n = psa->rgsabound[0].cElements;
    return std::copy (ptr, ptr + n, out);
}

Then you could have something like this:

std::list <short> l ;
My::read_safearray <short> (psa, std::back_inserter (l)) ;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.