Quote Originally Posted by cristinuccia View Post
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