C ++ уменьшить избыточность - PullRequest
1 голос
/ 28 марта 2019

У меня есть много функций C ++ в DLL, которую я сделал специально для вызова в Excel.

Я часто перехожу к этим функциям в качестве параметров OLE VARIANT, которые содержат SafeArrays.

Я написал функциюдействовать как защита, чтобы гарантировать, что переданный VARIANT действительно содержит SafeArray и что массив имеет соответствующий тип для случая, как с точки зрения типа данных, так и количества измерений.

Если эти три критерия удовлетвореныфункция возвращает указатель на первый элемент, а также имеет два параметра out для возврата количества элементов в каждом измерении (примечание: меня интересуют только 1d и 2d SafeArrays).

Вот функция:

PVOID SafeArrayDataPointer(VARIANT &v, const long &ArrayType, const long &ArrayDims, long &Elems1D, long &Elems2D) {

    SAFEARRAY* pSafeArray = NULL;

    if ( V_VT(&v) & VT_ARRAY ) {

        if ( ArrayType != (V_VT(&v) & VT_TYPEMASK) ) return NULL;

        pSafeArray = V_ARRAY(&v);
        if ( ArrayDims != pSafeArray->cDims ) return NULL;

        switch (ArrayDims) {
        case 2:
            Elems1D = (pSafeArray->rgsabound)[1].cElements;
            Elems2D = (pSafeArray->rgsabound)[0].cElements;
            break;
        case 1:
            Elems1D = (pSafeArray->rgsabound)[0].cElements;
            Elems2D = 0;
            break;
        default: 
            Elems1D = 0;
            Elems2D = 0;
            break;
        }

        return pSafeArray->pvData;

    } else return NULL;

}

Эта функция работает хорошо и позволяет мне удобно захватывать указатель данных и получать количество элементов в каждом измерении, вызывая так (например, vData - это VARIANT, переданный из Excel:

pDataArray = (VARIANT*) SafeArrayDataPointer(vData, VT_VARIANT, 2, Elems1D, Elems2D); if (pDataArray == NULL) goto error1;

Однако есть две вещи, которые мне не нравятся в этом:

1.) Так какunction возвращает PVOID Я должен привести к соответствующему типу указателя ... но это избыточно, так как я уже указал во втором аргументе, какой тип массива должен содержаться в VARIANT.Например, в другой ситуации мне может потребоваться убедиться, что массив имеет длинные значения:

pDataArray = (long*) SafeArrayDataPointer(vData, VT_I4, 1, Elems1D, Junk); if (pDataArray == NULL) goto error1;

Опять же, это работает нормально, но это избыточно.Я бы предпочел некоторый механизм, который позволяет функции возвращать правильный тип указателя.И я надеюсь, что это можно сделать без шаблонов.

2.) Я не могу понять, как НЕ иметь параметр Junk во втором примере, где я указываю, что массив должен быть 1d.Очевидно, что в таком случае во втором измерении нет элементов.

Ответы [ 2 ]

1 голос
/ 28 марта 2019

Если вы не хотите использовать шаблоны и вам не нужны специализированные функции, вы можете использовать брутто-макрос , чтобы решить первую проблему, связанную с необходимостью приводить из PVOID каждый раз, когда вы Звоните SafeArrayVariantPointer:

Примерно так:

#define CAST_TYPE_VT_VARIANT (VARIANT *)
#define CAST_TYPE_VT_LONG (long *)

#define SAFE_ARRAY_DATA_POINTER(vData, vType, dimSize, elems1D, elems2D) \
    CAST_TYPE_##vType SafeArrayDataPointer((vData), (vType), (dimSize), (elems1D), (elems2D))

Тогда вы можете позвонить, как:

VARIANT *pDataArray = SAFE_ARRAY_DATA_POINTER(vData, VT_VARIANT, 2, Elems1D, Elems2D);

Но сначала вам нужно изменить сигнатуру метода, чтобы аргумент ArrayType принимался как long, а не const long &.

Обратите внимание, что предполагается, что второй параметр SAFE_ARRAY_DATA_POINTER макрос должен быть литералом, который соответствует одному из определенных вами макросов CAST_TYPE_ *. Это не может быть переменной.

Для вас второй вопрос о избыточном параметре Junk, вы можете создать перегруженную функцию SafeArrayDataPointer, которая возвращает только размер первого измерения. Он может вызвать первую версию SafeArrayDataPointer и отбросить размер второго измерения.

Что-то вроде:

PVOID SafeArrayDataPointer(VARIANT &v, long ArrayType, const long &ArrayDims, long &Elems1D) 
{
    long Elems2D;
    PVOID *toReturn = SafeArrayDataPointer(v, ArrayType, ArrayDims, Elems1D, Elems2D);
    if (Elems2D != 0) toReturn = NULL;
    return toReturn;
}

Однако, чтобы решить эту проблему, я бы, вероятно, использовал шаблоны.

Во-первых, создайте набор array_type_traits классов, которые выставляют typedef для вашего типа приведения с длинным значением VT_LONG, VT_VARIANT и т. Д.

//Generic array_type_traits class
template<long array_type>
class array_type_traits 
{
public:
    typedef PVOID cast_type;
};

//Specialized for VT_LONG
template<>
class array_type_traits<VT_LONG>
{
public:
    typedef long * cast_type; 
};

//Specialized for VT_VARIANT
template<>
class array_type_traits<VT_VARIANT>
{
public:
    typedef VARIANT * cast_type;
};

Продолжайте специализировать их для каждого типа VT_ *, который у вас есть.

Далее, инкапсулируйте вашу SafeArrayDataPointer функцию внутри класса SafeArrayDataPointerBase.

//Base class which has one static function Get() that returns a PVOID
class SafeArrayDataPointerBase
{
protected:
    static PVOID Get(VARIANT& vData, long vType, long dimSize, long& elems1D, long& elems2D)
    {
        // Place your SafeArrayDataPointer function code here 
    }
};

Теперь создайте свой класс, который будет вызывать `SafeArrayDataPointerBase :: Get (), а затем приведите результат к правильному типу.

template<long ArrayType>
class SafeArrayDataPointer : public SafeArrayDataPointerBase
{
public:
    typedef typename array_type_traits<ArrayType>::cast_type cast_type;

    static cast_type Get(VARIANT& v, long ArrayDims, long& Elems1D, long& Elems2D)
    {
        return (cast_type) SafeArrayDataPointerBase::Get(v, ArrayDims, ArrayType, Elems1D, Elems2D);
    }
};

И, наконец, вы бы назвали класс шаблона следующим образом:

VARIANT *vp = SafeArrayDataPointer<VT_VARIANT>::Get(v, ArrayDims, Elems1D, Elems2D); 
long *vl = SafeArrayDataPointer<VT_LONG>::Get(v, ArrayDims, Elems1D, Elems2D);
0 голосов
/ 28 марта 2019

Что-то вроде это :

template<int vt> struct vt_type_disp;
template<> struct vt_type_disp<VT_I4> { typedef LONG type; };
template<> struct vt_type_disp<VT_VARIANT> { typedef VARIANT type; };

template<int vt> using vt_type = typename vt_type_disp<vt>::type;


template<int vtElem>
auto unpack_array(VARIANT& v, ULONG& d1_size)
{
    if ((V_VT(&v) & VT_ARRAY) && (V_VT(&v) & VT_TYPEMASK) == vtElem)
    {
        SAFEARRAY* p = (V_VT(&v) & VT_BYREF) ? *V_ARRAYREF(&v) : V_ARRAY(&v);

        if (p->cDims == 1)
        {
            d1_size = p->rgsabound[0].cElements;
            return static_cast<vt_type<vtElem>*>(p->pvData);
        }
    }
    return static_cast<vt_type<vtElem>*>(nullptr);
}

template<int vtElem>
auto unpack_array(VARIANT& v, ULONG& d1_size, ULONG& d2_size)
{
    if ((V_VT(&v) & VT_ARRAY) && (V_VT(&v) & VT_TYPEMASK) == vtElem)
    {
        SAFEARRAY* p = (V_VT(&v) & VT_BYREF) ? *V_ARRAYREF(&v) : V_ARRAY(&v);

        if (p->cDims == 2)
        {
            d1_size = p->rgsabound[1].cElements;
            d2_size = p->rgsabound[0].cElements;
            return static_cast<vt_type<vtElem>*>(p->pvData);
        }
    }
    return static_cast<vt_type<vtElem>*>(nullptr);
}

// functions to export from dll (if you need them)
auto unpack_array_I4_1D(VARIANT &v, ULONG& dim_size) { return unpack_array<VT_I4>(v, dim_size); }
auto unpack_array_I4_2D(VARIANT &v, ULONG& d1_size, ULONG& d2_size) { return unpack_array<VT_I4>(v, d1_size, d2_size); }
auto unpack_array_VARIANT_1D(VARIANT &v, ULONG& dim_size) { return unpack_array<VT_VARIANT>(v, dim_size); }
auto unpack_array_VARIANT_2D(VARIANT &v, ULONG& d1_size, ULONG& d2_size) { return unpack_array<VT_VARIANT>(v, d1_size, d2_size); }
// etc

Примечание: ваш код не обрабатывает VT_BYREF должным образом

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...