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.