1

I'm writing a C++ DLL to be accessed from Excel VBA (it is only completing mathematical equations and doesn't need to be accessed from the worksheet). The DLL is intended to replace current VBA code and I want to write it in C/C++ for performance.

Since I'm am using existing code and simply want to replace the current VBA function with the new DLL I have to use VBA Variants containing single vector arrays. The following is an abbreviated example of the VBA calling code:

Sub VBACaller()
    Dim InputR0(1 To 5) As Variant, vResult As Variant

    InputR0(1) = 26.8
    InputR0(2) = 27.8
    InputR0(3) = 28.8
    InputR0(4) = 29.8

    vResult = ReadArrayVBA(InputR0)
End Sub

I can pass this Variant array to my C++ function either ByVal or ByRef as a VARIANT data type and it appears to be received correctly. My problems occur when I try to read the contents of the array after converting it into a SAFEARRAY using SafeArrayAccessData. The SAFEARRAY conversion seems to work but the contents of the array aren't correct. The following is my C++ code:

VARIANT __stdcall ReadArrayByRef(VARIANT *input0)
{
//Variable to assign value from Array
double d_m = 0;

//Check if this is a variant type or not
if (V_VT(input0) & VT_ARRAY)
{
    SAFEARRAY* pSafeArrayInput0 = NULL;
    pSafeArrayInput0 = V_ARRAY(input0);

    double* pVals;

    HRESULT hr = SafeArrayAccessData(pSafeArrayInput0, (void **)&pVals); // direct access to SA memory
    if (SUCCEEDED(hr))
    {
        long lLBound = -1, lUBound = 1;  // get array bounds
        SafeArrayGetLBound(pSafeArrayInput0, 1, &lLBound);
        SafeArrayGetUBound(pSafeArrayInput0, 1, &lUBound);

        if (lLBound > -1 && lUBound > -1)
        {
            d_m =  pVals[1];
        }
        SafeArrayUnaccessData(pSafeArrayInput0);
    }
}

//Output
VARIANT v;
VariantInit(&v);
v.vt = VT_R8;
v.dblVal = d_m;

return v;
}    

The examples I've found seem to indicate that the .parray should hold the data, however inspection of input0 indicates that it is in .pparray. If I try to assign pSafeArrayInput0 = input0.pparray it gives an error. The value d_m returns is along the lines of 1.05319234616515E-307.

If I change the input to ByVal then I can access the elements of the array correctly using the following C++ code (the only difference being the address of input0 is being accessed by the SAFEARRAY.

VARIANT __stdcall ReadArrayByVal(VARIANT input0)
{
//Variable to assign value from Array
double d_m = 0;

//Check if this is a variant type or not
if (V_VT(&input0) & VT_ARRAY)
{
    SAFEARRAY* pSafeArrayInput0 = NULL;
    pSafeArrayInput0 = V_ARRAY(&input0);

    double* pVals;

    HRESULT hr = SafeArrayAccessData(pSafeArrayInput0, (void **)&pVals); // direct access to SA memory
    if (SUCCEEDED(hr))
    {
        long lLBound = -1, lUBound = 1;  // get array bounds
        SafeArrayGetLBound(pSafeArrayInput0, 1, &lLBound);
        SafeArrayGetUBound(pSafeArrayInput0, 1, &lUBound);

        if (lLBound > -1 && lUBound > -1)
        {
            d_m =  pVals[1];
        }
        SafeArrayUnaccessData(pSafeArrayInput0);
    }
}

VARIANT v; 
VariantInit(&v);
v.vt = VT_R8;
v.dblVal = d_m;

return v;
}

All the examples I've found indicate that passing the VARIANT input by a pointer should works per my ReadArrayByRef function (Ie http://support.microsoft.com/kb/167668 which is slightly different but the same at the points I'm after).

What am I doing wrong in my ByRef function?

CuberChase
  • 4,458
  • 5
  • 33
  • 52
  • You're not actually checking the type of the array, only that it is an array (e.g. you're looking at the `VT_ARRAY` flag but nothing else). I don't know anything really about Excel VBA but for example VBScript only supports arrays of `VT_VARIANT`, and you seem to be assuming you have an array of `VT_R8`. – Jonathan Potter Jul 26 '13 at 13:41
  • Yeah, the final code should check for a double data type however I haven't bothered with the example here because I have verified by debugging that each element is a `VT_R8`. Changing each element to a different data type corresponds with the correct data type in the `VARIANT` array (ie an integer will come in as a `VT_I2`. – CuberChase Jul 26 '13 at 13:56
  • You should also be checking for `VT_BYREF` I guess, since it seems like the array is coming in as a reference - as you've discovered. To access the array in `pparray` you need to dereference it - i.e. `pSafeArrayInput0 = *input0.pparray;` – Jonathan Potter Jul 26 '13 at 14:06
  • Thanks for the suggestion I think you're right that it's a matter of getting the SafeArray from `.pparray`. I can check for the `VT_BYREF` ok but when I try `pSafeArrayInput0 = *input0.pparray` I get an error of 'expression must have class'. In the `ByVal` case `pSafeArrayInput0 = V_ARRAY(&input0);` is the equivalent of `pSafeArrayInput0 = input0.parray` but that doesn't work for `.pparray`. – CuberChase Jul 26 '13 at 21:06
  • Does `pSafeArrayInput0 = *(input0.pparray);` work? – Jonathan Potter Jul 26 '13 at 21:16
  • No, same error with the red line under the `input0` – CuberChase Jul 26 '13 at 21:29
  • Sorry, I was looking at your second example where your function is taking the VARIANT by value, not your first where it's a pointer. Try `pSafeArrayInput0 = *input0->pparray;` (or `pSafeArrayInput0 = *V_ARRAYREF(input0);`) – Jonathan Potter Jul 26 '13 at 21:35
  • Sensational! That was it. Please make this an answer so I can give you rep! – CuberChase Jul 26 '13 at 21:40

1 Answers1

2

You should check the VT_BYREF flag, and if it is set, dereference the pointer to access the array like so:

pSafeArrayInput0 = *V_ARRAYREF(input0);
Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79