
Originally Posted by
cristinuccia
Actually, I want to develop a function in which one of its output parameters is an array of strings. Based on this I thought to pass it an array of empty strings and make the function fill up these strings.
As long as you work with the dedicated functions from the SafeArray-API and the BSTR-API (mostly defined in oleauto.h),
you can stay conform to the VB-end, since the above functions are at play also "on the other side".
The only problem as I see it - is (for a C-programmer) to understand (or avoid) those weird automatic and implicit BSTR-ANSI-BSTR conversions on the VB-end
(which take place as soon as one is passing something that has BSTRs in it, to a Declared Dll-function-call.)
To avoid those conversions (and mostly these days one has #define UNICODE enabled also at the C-end) one should pass only Int-Typed Pointers to the Dll-functions - and thus hide (from VB) that those Params point to "something with Strings in it".
So, well - here's my take at an Unicode-capable exchange with a C-Dll-Source (which internally has to be switched to #define UNICODE)
The C-side with two functions (one which is just a "passive reader" - but the other function accepts even uninitialized SafeArrays, does allocations and returns a properly filled SafeArray of BSTRs back to VB):
Code:
DLL_EXPORT void __stdcall ReadBStrSafeArray(SAFEARRAY** ppsa)
{
if (!ppsa) return;
SAFEARRAY* psa = *ppsa; //deref, because from VB we got the pointer to the Variable which *hosts* the SafeArray, not the SafeArray itself
if (!psa) return;
BSTR* bstrArr;
HRESULT hr;
hr = SafeArrayAccessData(psa, (void **) &bstrArr); //map the pvData-pointer and lock the SafeArray
if (hr == S_OK) {
for(long i=0; (i < psa->rgsabound[0].cElements); i++) {
MessageBox (0, bstrArr[i], L"Unicode-capable BSTRArr-Enumeration from within C-DLL", MB_ICONINFORMATION);
}
}
hr = SafeArrayUnaccessData(psa); //release the locking of the SafeArray after our loop is finished
}
DLL_EXPORT void __stdcall InitAndFillBStrSafeArray(SAFEARRAY** ppsa)
{
if (!ppsa) return;
SAFEARRAY* psa = *ppsa; //deref again
if (psa) SafeArrayDestroy(psa); //cleanup (and automatically free the current Arr-Members, if there are any)
psa = SafeArrayCreateVector(VT_BSTR, 0, 3);
*ppsa = psa; //ensure, that our new target-array is returned "ByRef" in the passed VarPtr
if (!psa) return;
BSTR* bstrArr;
HRESULT hr;
hr = SafeArrayAccessData(psa, (void **) &bstrArr); //map the pvData-pointer and lock the SafeArray
if (hr == S_OK) {
BSTR btmpLeft, btmpRight;
for(long i=0; (i < psa->rgsabound[0].cElements); i++) {
btmpLeft = SysAllocString(L"C-entered WideChar-Item at "); //just to demonstrate a BSTR-concatenation at this occasion
hr = VarBstrFromI4(i, 0, 0, &btmpRight); //the left part was created above - here comes the right part ("casted" from an Int32)
if (hr == S_OK) VarBstrCat(btmpLeft, btmpRight, &bstrArr[i]); //store the concat-result in our so far empty &bstrArr[i]-slot
SysFreeString(btmpLeft); //VarBstrCat doesn't free the left and right BSTR-Vars...
SysFreeString(btmpRight); //so we do it by hand here
}
}
hr = SafeArrayUnaccessData(psa); //release the locking of the SafeArray after our loop is finished
}
And here's the VB-side, which shows a method - how to retrieve the VarPtr of a SafeArray whilst avoiding any ANSI-conversions.
Code:
'Into a Form, then click the Form
Option Explicit
Private Declare Sub MemCopy Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal CB&)
Private Declare Sub ReadBStrSafeArray Lib "d:\test.dll" (ByVal pSafeArrVar As Long)
Private Declare Sub InitAndFillBStrSafeArray Lib "d:\test.dll" (ByVal pSafeArrVar As Long)
Private Sub Form_Click()
Dim i As Long
'first we feed something into the C-Dll (MessageBoxes are raised from within the C-Dll-Routine)
ReDim SArrIn(0 To 2) As String
For i = 0 To UBound(SArrIn)
SArrIn(i) = "VB-entered WideChar-Item at " & i
Next
ReadBStrSafeArray GetSafeArrVarPtr(SArrIn)
'now we retrieve a BSTR-SafeArray from the C-Dll without the need to initialize it on the VB-end
Dim SArrOut() As String
InitAndFillBStrSafeArray GetSafeArrVarPtr(SArrOut)
For i = 0 To UBound(SArrOut)
MsgBox SArrOut(i)
Next
End Sub
Private Function GetSafeArrVarPtr(Arr) As Long 'a method, which works without any implicit ANSI-conversions on String-arrays
If IsArray(Arr) Then MemCopy GetSafeArrVarPtr, ByVal VarPtr(Arr) + 8, 4
End Function
HTH
Olaf