Passing a SAFEARRAY of BSTRs between a C++ DLL and VB 2008

1503 views
Skip to first unread message

Kamel@discussions.microsoft.com George Kamel

unread,
Nov 17, 2009, 1:22:02 PM11/17/09
to
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

unread,
Nov 18, 2009, 12:04:57 PM11/18/09
to
"George Kamel" <George Ka...@discussions.microsoft.com> ha scritto nel
messaggio news:E1A75571-3036-4B15...@microsoft.com...

> 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);

I would suggest you to use double indirection for the SAFEARRAY parameter,
i.e. use SAFEARRAY** instead of SAFEARRAY*.

Giovanni

George Kamel

unread,
Nov 19, 2009, 2:26:05 PM11/19/09
to
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

unread,
Nov 20, 2009, 6:49:24 AM11/20/09
to
"George Kamel" <Georg...@discussions.microsoft.com> ha scritto nel
messaggio news:1C142ED1-A496-44CB...@microsoft.com...

> Dear Giovanni,
> Many thanks for your reply.

George: You are welcome.


> 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.


> 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

unread,
Nov 20, 2009, 10:53:59 AM11/20/09
to
"George Kamel" <Georg...@discussions.microsoft.com> ha scritto nel
messaggio news:1C142ED1-A496-44CB...@microsoft.com...

> 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

unread,
Nov 23, 2009, 4:40:01 AM11/23/09
to
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

unread,
Nov 23, 2009, 5:25:34 AM11/23/09
to
"George Kamel" <Georg...@discussions.microsoft.com> ha scritto nel
messaggio news:1037E574-CBA8-4B5D...@microsoft.com...

> 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

Reply all
Reply to author
Forward
0 new messages