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 fromVT_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
SAFEARRAY
\$\endgroup\$