Using Arrays of Data Structures

You cannot use the SafeArrayCreate function to create a new array of user-defined structures. Instead, you must use the SafeArrayAllocDescriptor function to create the array descriptor, then use the SafeArrayAllocData function to allocate space for the array data, and finally use the SafeArrayAccessData function to return a pointer to the data. The SafeArrayAccessData function locks the array data; when you are done with the array, you should call the SafeArrayUnaccessData function to unlock it.

You cannot replace an existing array, so if your Visual Basic code passes a dimensioned array, you must redimension it. Remember to free any existing BSTR pointers in the array before overwriting them.

The following example creates or redimensions an array of data structures and then copies an array of strings into the structures, adding the string length to each structure. Any existing BSTR data in the array is freed before new data is copied into the array.

short WINAPI StructArray(LPSAFEARRAY *ppsaArg, 
    LPSAFEARRAY *ppsaStr)
{
    ARG *parg;
    SAFEARRAY *psa;
    BSTR *pbstr;
    unsigned long i, cElements;
    #define BUFF_SIZE 1024
    TCHAR szBuff[BUFF_SIZE];

    if (*ppsaStr == NULL)
        return -1;

    cElements = (*ppsaStr)->rgsabound[0].cElements;

    if (*ppsaArg == NULL) // create a new array
    {

        if (FAILED(SafeArrayAllocDescriptor(1, &psa)))
            return -3;

        // set up the SAFEARRAY structure
        // and allocate data space

        psa->fFeatures = 0;
        psa->cbElements = sizeof(ARG);
        psa->rgsabound[0].cElements = cElements;
        psa->rgsabound[0].lLbound = (*ppsaStr)->rgsabound[0].lLbound;

        if (FAILED(SafeArrayAllocData(psa))) 
        {
            SafeArrayDestroyDescriptor(psa);
            return -4;
        }

        // get a pointer to the new data

        if (FAILED(SafeArrayAccessData(psa, 
                (void HUGEP* FAR*)&parg))) 
        {
            SafeArrayDestroy(psa);
            return -5;

        }
    } 
    else // fail since we can't redimension
    {
            return -6;

        // get a pointer to the old data

        if (FAILED(SafeArrayAccessData(*ppsaArg, 
                (void HUGEP* FAR*)&parg)))
            return -7;
    }

    // get a pointer to the string array data

    if (FAILED(SafeArrayAccessData(*ppsaStr, 
            (void HUGEP* FAR*)&pbstr)))
        return -8;

    // allocate strings in the structure array and 
    // fill them with strings from the string array.
    // free any old BSTRs in the structure

    for (i = 0; i < cElements; i++) 
    {
        SysFreeString(parg[i].bstr);//SysStringByteLen(pbstr[i])
        MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (LPSTR)pbstr[i], -1, 
            szBuff, sizeof(szBuff));
        parg[i].bstr = SysAllocString(szBuff);
        parg[i].i = SysStringLen(parg[i].bstr);
    }

    // release pointers and move the structure
    // array pointer to the new array if we created one

    SafeArrayUnaccessData(*ppsaStr);
    
    if (*ppsaArg == NULL) 
    {
        SafeArrayUnaccessData(psa);
        *ppsaArg = psa;
    }
    else 
        SafeArrayUnaccessData(*ppsaArg);
        
    return 0;
}

Declared and called from Visual Basic:

Declare Function StructArray Lib "debug\ADVDLL.DLL" _
    (x() As ARG, s() As String) As Integer

Sub StructArrayTest()
    Dim x() As ARG
    Dim s(1 To 4) As String
    s(1) = "yellow"
    s(2) = "orange"
    s(3) = "blue"
    s(4) = "green"
    n = StructArray(x, s)
    If n = 0 Then
        Worksheets(1).Activate
        Range("a1:c25").Clear
        For i = LBound(x) To UBound(x)
            Cells(i + 1, 1) = i
            Cells(i + 1, 2) = x(i).str
            Cells(i + 1, 3) = x(i).i
        Next
    Else
        MsgBox "StructArray failed, returned" & n
    End If
End Sub

You will note in this code that we use MultiByteToWideChar on the string before we place it into the structure. This is the one exception to the rule that Excel passes and returns ANSI strings. Strings returned to Excel inside a structure must be UNICODE. This function converts the strings to UNICODE before creating a BSTR to place in the structure.