Discussion:
Passing a SAFEARRAY of BSTRs between a C++ DLL and VB 2008
(too old to reply)
George Kamel
2009-11-17 18:22:02 UTC
Permalink
Hi,
I am having a problem in passing a SAFEARRAY of BSTRs from a C++ DLL to a VB
2008 application. I am able to pass an array of doubles without any issue.
However, coming to a SAFEARRAY, it does not seem to be passing anything back,
i.e. the array in VB remains empty after calling the C++ function.

This is the code of the DLL that I am using, which compiles okay and is
called without returning any errors:

extern "C" int __declspec(dllexport) __stdcall DLLTestFunction(SAFEARRAY*
Label, double* Value);

int __stdcall DLLTestFunction(SAFEARRAY* Label, double* Value)
{
USES_CONVERSION;
char buffer[20] = {""}; // used to store ANSI string

HRESULT hr = S_OK;

SAFEARRAYBOUND aDim[1];
aDim[0].lLbound = 0; // Visual Basic arrays start with index 0
aDim[0].cElements = 20; // Number of SAFEARRAY elements to create

Label = SafeArrayCreate(VT_VARIANT, 1, aDim); // Create SAFEARRAY
if (Label != NULL)
{
long aLong[1];

// iterate over array adding VARIANTs of type VT_BSTR
for (unsigned long i = aDim[0].lLbound ; i < (aDim[0].cElements +
aDim[0].lLbound) ; i++)
{
VARIANT vOut; // Create VARIANT and
VariantInit(&vOut); // initialise it and
vOut.vt = VT_BSTR; // set its type

strcpy_s(buffer, "SomeText"); // Load buffer with text to copy to
SAFEARRAY
vOut.bstrVal = ::SysAllocString(A2BSTR(buffer)); // system wide "new"

aLong[0] = i; // set index value
if (hr = SafeArrayPutElement(Label, aLong, &vOut)) // copy VARIANT to
SAFEARRAY
{
VariantClear(&vOut); // release BSTR from memory on error
SafeArrayDestroy(Label); // does a deep destroy on error
return 1; // return non-zero value
}

VariantClear(&vOut); // does a deep destroy of source VARIANT

Value[i] = i; // Set Value array to for loop index
} // end iteration
}
return 0;
}


I have tested the C++ code by itself within a main() function, initialising
the SAFEARRAY at the beginning:

SAFEARRAY *Label;

and by the end of the function, I am getting the SAFEARRAY filled as
expected. So the problem seems to be in passing the array back to VB.

The following is the code that I am using to call the code from within VB:

<DllImport("MyDLL.dll")> _
Public Function DLLTestFunction( _
<MarshalAs(UnmanagedType.SafeArray,
SafeArraySubType:=VarEnum.VT_BSTR)> _
ByRef Label() As String, _
ByRef Value As Double) As Integer
End Function

Dim Label(0 To 19) As String
Dim Value(0 To 19) As Double
Dim RetVal As Integer

RetVal = DLLTestFunction(Label, Value(0))


As the C++ code runs fine by itself, I think the problem must be in the way
I am calling the function in VB, or in the C++ DLL function header.

Any help to resolve this would be greatly appreciated.

George
Giovanni Dicanio
2009-11-18 17:04:57 UTC
Permalink
Post by George Kamel
I am having a problem in passing a SAFEARRAY of BSTRs from a C++ DLL to a VB
2008 application. I am able to pass an array of doubles without any issue.
However, coming to a SAFEARRAY, it does not seem to be passing anything back,
i.e. the array in VB remains empty after calling the C++ function.
This is the code of the DLL that I am using, which compiles okay and is
extern "C" int __declspec(dllexport) __stdcall DLLTestFunction(SAFEARRAY*
Label, double* Value);
I would suggest you to use double indirection for the SAFEARRAY parameter,
i.e. use SAFEARRAY** instead of SAFEARRAY*.

Giovanni
George Kamel
2009-11-19 19:26:05 UTC
Permalink
Dear Giovanni,
Many thanks for your reply. I have added the double indirection to the
SAFEARRAY parameter of the function headers as you recommended (and, of
course, a single indirection on each occurrence "Label" within the function),
but I am now getting a SafeArrayTypeMismatchException in VB. Despite the
exception, I am seeing in VB the array of doubles being passed back okay.

Having "stepped into" the C++ DLL call from VB, and I notice that the array
of doubles ("Value") has a value of 0.00000000000000000 throughout the
execution of the DLL function (i.e. I cannot see the individual elements of
Value being set). For the "Label" array on the other hand, I can see the BSTR
values being placed in the SAFEARRAY upon each iteration of the loop.

I am not sure if these observations will help or not in determining the
cause of the problem?

George
Giovanni Dicanio
2009-11-20 11:49:24 UTC
Permalink
Post by George Kamel
Dear Giovanni,
Many thanks for your reply.
George: You are welcome.
Post by George Kamel
I have added the double indirection to the
SAFEARRAY parameter of the function headers as you recommended (and, of
course, a single indirection on each occurrence "Label" within the function),
but I am now getting a SafeArrayTypeMismatchException in VB. Despite the
exception, I am seeing in VB the array of doubles being passed back okay.
I mapped this native C prototype:

extern "C" HRESULT __stdcall DLLTestFunction(
SAFEARRAY ** ppsaLabel
)

to this VB.NET code:

<DllImport("TestDll.dll")> _
Private Shared Function DLLTestFunction( _
<MarshalAs(UnmanagedType.SafeArray,
SafeArraySubType:=VarEnum.VT_VARIANT)> _
<System.Runtime.InteropServices.Out()> _
ByRef Label() As String) _
As Integer
End Function

and it works just fine.

I built a VB.NET WinForms test app. I put a button ('ButtonTest') and a
listbox ('ListBoxLabels') on the form, and wrote this code for button Click
event:

<code language="VB.NET">

Private Sub ButtonTest_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles ButtonTest.Click
Dim Result As Integer
Dim Labels(0 To 19) As String

ListBoxLabels.Items.Clear()

Result = DLLTestFunction(Labels)
If Result <> 0 Then
MessageBox.Show("Error in calling DLLTestFunction")
Return
End If

For i As Integer = 0 To Labels.Length - 1
ListBoxLabels.Items.Add(Labels(i))
Next
End Sub

</code>

The following code is the implementation of C++ DLL, with comments:

<code language="C++">

// FILE: TestDll.cpp


#include <atlstr.h> // CString
#include <atlcomcli.h> // CComVariant
#include <atlsafe.h> // CComSafeArray


extern "C" HRESULT __stdcall DLLTestFunction(
SAFEARRAY ** ppsaLabel
)
{
// Check pointer parameter
if ( ppsaLabel == NULL )
return E_POINTER;

// Clear SAFEARRAY pointer.
// It will be assigned to a valid SAFERRAY on success.
*ppsaLabel = NULL;

// Result of operations
HRESULT hr = S_OK;


//
// Create SAFEARRAY of VARIANT's
//
const int elementCount = 20;
CComSafeArray<VARIANT> sa;
hr = sa.Create( elementCount );
if ( FAILED(hr) )
return hr;

//
// Iterate over array adding VARIANTs of type VT_BSTR
//
try
{
for ( int i = 0; i < elementCount; i++ )
{
// Format string
CStringW text;
text.Format( L"Element #%d", i );

// Build a VARIANT storing the BSTR
CComVariant var( text );

// Add it to SAFEARRAY
hr = sa.SetAt( i, var );
if ( FAILED(hr) )
return hr;
}
}
catch (const CAtlException & ex)
{
// Convert exceptions to raw HRESULT's
// and return HRESULT value to caller.
return ex.m_hr;
}

// Copy SAFEARRAY to caller
*ppsaLabel = sa.Detach();

// All right
return S_OK;
}

</code>

Note that the C++ code is simplified by the use of C++ RAII and ATL helper
classes like CComSafeArray, CComVariant and CStringW, instead of raw
SAFERRAY, VARIANT and wchar_t/SysAllocString/strcpy_s, functions.
Post by George Kamel
Having "stepped into" the C++ DLL call from VB, and I notice that the array
of doubles ("Value") has a value of 0.00000000000000000 throughout the
execution of the DLL function (i.e. I cannot see the individual elements of
Value being set). For the "Label" array on the other hand, I can see the BSTR
values being placed in the SAFEARRAY upon each iteration of the loop.
You may want to use a SAFEARRAY to pass the 'Value' array as well.


HTH,
Giovanni
Giovanni Dicanio
2009-11-20 15:53:59 UTC
Permalink
Post by George Kamel
Having "stepped into" the C++ DLL call from VB, and I notice that the array
of doubles ("Value") has a value of 0.00000000000000000 throughout the
execution of the DLL function (i.e. I cannot see the individual elements of
Value being set).
Assuming that you have a managed (VB.NET) allocated array, and you want to
pass it to a native C/C++ function to fill it with some data, you can use
code like this:

<code language="VB.NET">

<DllImport("TestDll.dll")> _
Private Shared Sub DLLTestFillValues( _
ByRef Values As Double, _
ByVal Count As Integer)
End Sub

</code>

This is a native function to fill the array with some data, just for testing
purposes:

<code language="C++">

extern "C" void __stdcall DLLTestFillValues(
double * Values,
int Count
)
{
for (int i = 0; i < Count; i++)
{
Values[i] = i;
}
}

</code>

This VB.NET code runs fine:

<code language="VB.NET">

Dim Values(0 To 19) As Double

DLLTestFillValues(Values(0), Values.Length)

</code>


Here is an MSDN page that you may find useful:

"Default Marshaling for Arrays"
http://msdn.microsoft.com/en-us/library/z6cfh6e6%28VS.80%29.aspx

HTH,
Giovanni
George Kamel
2009-11-23 09:40:01 UTC
Permalink
Giovanni, these solutions have worked perfectly. Thank you very much for your
time and effort - you've been a tremendous help.

Kind regards,
George
Giovanni Dicanio
2009-11-23 10:25:34 UTC
Permalink
Post by George Kamel
Giovanni, these solutions have worked perfectly. Thank you very much for your
time and effort - you've been a tremendous help.
George: you are welcome.

Giovanni

Loading...