Search code examples
cexcelvbamacosvariant

Return a Variant from a c dll to Excel VBA on macos


In Paulo Bueno's project https://github.com/buenop/MinXL he returns a Variant from a C++ dll, e.g.

Declare PtrSafe Function IncrementArrayBy _
Lib "/Library/Application Support/Microsoft/YourLibrary.dylib" _
(ByRef v as Variant, ByVal d as Double) As Variant 

I would like to do the same, but in C. Here's is my reduced code so far:

typedef uint16_t VARTYPE;  
typedef uint16_t WORD; 
typedef double DOUBLE;

typedef struct {
    VARTYPE vt;
    WORD    wReserved1;
    WORD    wReserved2;
    WORD    wReserved3;
    union {
        DOUBLE       dblVal;
    } data;
} VARIANT2;

enum VARENUM {
    VT_DOUBLE       = 0x0005,
};

VARIANT2 * new_VT_DOUBLE_PTR(double x) {
    VARIANT2 *answer = (VARIANT2 *) malloc(sizeof(VARIANT2));
    answer->vt = VT_DOUBLE;
    answer->data.dblVal = x;
    return answer;
}

VARIANT2 new_VT_DOUBLE(double x) {
    VARIANT2 answer;
    answer.vt = VT_DOUBLE;
    answer.data.dblVal = x;
    return answer;
}
Private Declare PtrSafe Function jinni_new_VT_DOUBLE _
Lib "/Library/Application Support/Microsoft/jinni.dylib" Alias "new_VT_DOUBLE" _
(ByVal x As Double) As Variant
Private Declare PtrSafe Function jinni_new_VT_DOUBLE_PTR _
Lib "/Library/Application Support/Microsoft/jinni.dylib" Alias "new_VT_DOUBLE_PTR" _
(ByVal x As Double) As Variant

Function newDoublePtr(x As Double) As Variant
    newDoublePtr = jinni_new_VT_DOUBLE_PTR(x)
End Function

Function newDouble(x As Double) As Variant
    newDouble = jinni_new_VT_DOUBLE(x)
End Function

At the moment both these functions are returning an Empty to VBA. This makes sense as if I look at the 16 bytes from answer the first few (i.e. the vt are 0), e.g. using this code.

Private Declare PtrSafe Sub memcpy _
Lib "/usr/lib/libSystem.dylib" _
(ByVal dest As LongPtr, ByVal src As LongPtr, ByVal count As Long)

Function peekMem(ptr As LongPtr, size As Long) As Byte()
    ReDim answer(1 To size) As Byte
    memcpy VarPtr(answer(1)), ptr, size
    peekMem = answer
End Function

Sub testVar()
    Dim fred As Variant, joe As Variant, p As LongPtr

    fred = 1#
    joe = peekMem(VarPtr(fred), 16)

    p = jinni_new_VT_DOUBLE_PTR2(5#)
    joe = peekMem(VarPtr(fred), 16)   ' this has the correct values is it

    fred = jinni_new_VT_DOUBLE_PTR(5#)
    joe = peekMem(VarPtr(fred), 16)   ' but this doesn't

    fred = jinni_new_VT_DOUBLE(5#)
    joe = peekMem(VarPtr(fred), 16)   ' and neither does this

End Sub

Any ideas how to get this working? Is vba expecting a VARIANT **?

I don't really want to copy the data into a preallocated variant as I like the simplicity of returning a Variant on the stack.


Paulo confirmed he returns the variant on the stack, reducing the problem to, why does the following show up as 16 zeros?

VARIANT2 doubleInVariant(double x, VARIANT2 * out) {
    VARIANT2 answer;
    answer.vt = VT_R8;
    answer.data.dblVal = x;
    memcpy(out, &answer, sizeof(VARIANT2));
    return answer;
}

and

Private Declare PtrSafe Function jinni_doubleInVariant _
Lib "/Library/Application Support/Microsoft/jinni.dylib" Alias "doubleInVariant" _
(ByVal x As Double, ByRef cpy As Variant) As Variant

go figure


Paulo pointed out that a variant is 24 bytes not 16 bytes. So changing the variant struct to:

typedef struct {
    VARTYPE vt;
    WORD    wReserved1;
    WORD    wReserved2;
    WORD    wReserved3;
    union {
        DOUBLE       dblVal;
    } data;
     WORD padding[4];                // due to __VARIANT_NAME_4 in the tagVARIANT struct
} VARIANT2;

Everything now works - see https://github.com/coppertop-bones/jinni for the src code and an example spreadsheet.


Solution

  • Defining VARIANT with the extra padding that takes the struct from 16 bytes to 24 bytes works.

    typedef struct {
        VARTYPE vt;
        WORD    wReserved1;
        WORD    wReserved2;
        WORD    wReserved3;
        union {
            DOUBLE       dblVal;
        } data;
         WORD padding[4];                // due to __VARIANT_NAME_4 in the tagVARIANT struct
    } VARIANT2;
    

    I'm feeling a bit of a school boy but the results of a quick google do seem to suggest that 16 bytes is a common misconception. For anyone one encountering this I hope this helps.