vb.net Marshalling: Passing array of structure to dll

G

Guest

Does anyone know how to pass an array of structures to a DLL? Here's what
I'm doing... the VB.Net error is at the end.


*** C++ Structure Declaration:

typedef struct _SOME_STRUCT
{
char *pId;
char *pBuffer;
DWORD dwBufferLength;
DWORD dwBytesReturned;
DWORD dwStatus;
} SOME_STRUCT;

*** C++ Function Prototype:
SOME_API ULONG _stdcall GetData (char *pDevName, SOME_STRUCT *pSomeStruct,
unsigned long dwTotalStructs);


*** VB.NET Structure Declaration:
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Public Structure TEST_SOME_STRUCT
<MarshalAs(UnmanagedType.LPStr)> Public URL As String
<MarshalAs(UnmanagedType.LPArray)> Public Buffer() As Byte
<MarshalAs(UnmanagedType.U4)> Public BufferLength As Int32
<MarshalAs(UnmanagedType.U4)> Public BytesReturned As Int32
<MarshalAs(UnmanagedType.U4)> Public Status As Int32

Public Sub New(ByVal URL As String, ByVal BufferLength As Integer)
Me.URL = URL
Me.BufferLength = BufferLength
ReDim Buffer(BufferLength - 1)
End Sub
End Structure

*** VB.NET DLL Prototype (declaration):

<DllImport("mtmcrapi.dll")> Protected Shared Function GetData(<[In]()> ByVal
DeviceName As String, <MarshalAs(UnmanagedType.LPArray), [In](), Out()> ByRef
Info() As TEST_SOME_STRUCT, <[In]()> ByVal StructCount As Int32) As Int32
End Function

*** VB.NET Code that produces the error:

Dim MyBuffers() As TEST_SOME_STRUCT
ReDim MyBuffers(1)
MyBuffers(0) = New TEST_SOME_STRUCT("anyUrl1", 1024)
MyBuffers(1) = New TEST_SOME_STRUCT("anyUrl2", 4096)
Dim rc as Int32 = GetData(mDeviceName, MyBuffers, 2)

*** VB.NET Error:
An unhandled exception of type 'System.TypeLoadException' occurred in
MyApp.exe

Additionaql information: Can not marshal field Buffer of type
TEST_SOME_STRUCT: This type can not be marshaled as a structure field.
 
I

Imran Koradia

*** VB.NET Structure Declaration:
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Public Structure TEST_SOME_STRUCT
<MarshalAs(UnmanagedType.LPStr)> Public URL As String
<MarshalAs(UnmanagedType.LPArray)> Public Buffer() As Byte
<MarshalAs(UnmanagedType.U4)> Public BufferLength As Int32
<MarshalAs(UnmanagedType.U4)> Public BytesReturned As Int32
<MarshalAs(UnmanagedType.U4)> Public Status As Int32

Public Sub New(ByVal URL As String, ByVal BufferLength As Integer)
Me.URL = URL
Me.BufferLength = BufferLength
ReDim Buffer(BufferLength - 1)
End Sub
End Structure

You should be able to define pBuffer as StringBuilder since its a variable
length string.

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Public Structure TEST_SOME_STRUCT
<MarshalAs(UnmanagedType.LPStr)> Public URL As String
Public Buffer As System.Text.StringBuilder
<MarshalAs(UnmanagedType.U4)> Public BufferLength As Int32
<MarshalAs(UnmanagedType.U4)> Public BytesReturned As Int32
<MarshalAs(UnmanagedType.U4)> Public Status As Int32
Public Sub New(ByVal URL As String, ByVal BufferLength As Integer)
Me.URL = URL
Me.Buffer = New System.Text.StringBuilder(BufferLength)
End Sub
End Structure


* *** VB.NET DLL Prototype (declaration):
<DllImport("mtmcrapi.dll")> Protected Shared Function GetData(<[In]()> ByVal
DeviceName As String, <MarshalAs(UnmanagedType.LPArray), [In](), Out()> ByRef
Info() As TEST_SOME_STRUCT, <[In]()> ByVal StructCount As Int32) As Int32
End Function

Try declaring the function as:
<DllImport("mtmcrapi.dll")> Protected Shared Function _
GetData(ByVal DeviceName As String, _
<[In], Out> ByVal Info() As TEST_SOME_STRUCT, _
ByVal StructCount As Int32) As Int32
End Function



hope that helps..
Imran.
 
G

Guest

Imran Koradia said:
Try declaring the function as:
<DllImport("mtmcrapi.dll")> Protected Shared Function _
GetData(ByVal DeviceName As String, _
<[In], Out> ByVal Info() As TEST_SOME_STRUCT, _
ByVal StructCount As Int32) As Int32
End Function


hope that helps..
Imran.
Thanks for the suggestion Imran, but whether it is declared ByRef or ByVal,
I get the same result.
 
I

Imran Koradia

Did you change the Buffer field to StringBuilder (or something else)? I
believe you cannot mark fields with the UnmanagedType.LPArray attribute.
Either use the Stringbuilder or use an IntPtr and then use Marshal.Copy to
convert back and forth.

hope that helps..
Imran.

Mick said:
Imran Koradia said:
Try declaring the function as:
<DllImport("mtmcrapi.dll")> Protected Shared Function _
GetData(ByVal DeviceName As String, _
<[In], Out> ByVal Info() As TEST_SOME_STRUCT, _
ByVal StructCount As Int32) As Int32
End Function


hope that helps..
Imran.
Thanks for the suggestion Imran, but whether it is declared ByRef or ByVal,
I get the same result.
 
G

Guest

Imran Koradia said:
Did you change the Buffer field to StringBuilder (or something else)? I
believe you cannot mark fields with the UnmanagedType.LPArray attribute.
Either use the Stringbuilder or use an IntPtr and then use Marshal.Copy to
convert back and forth.

hope that helps..
Imran.

I have tried StringBuilder. I'm not certain, but because the buffer can be
populated with data that can contain byte values of zero (0), I believe this
would not be an acceptable solution. If this is not correct, please let me
know.

I believe the IntPtr may work, but in order to use an IntPtr, I would need
to declare and allocate space for a byte array, and then have the IntPtr
point to the address of the first element of that byte array so the dll call
could populate it. Do you know how I might accomplish this?

The function call requires that I pass a pointer to an allocated buffer.

Mick
 
I

Imran Koradia

Try this:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Public Structure TEST_SOME_STRUCT
<MarshalAs(UnmanagedType.LPStr)> Public URL As String
Public BuffPtr As IntPtr
<MarshalAs(UnmanagedType.U4)> Public BufferLength As Int32
<MarshalAs(UnmanagedType.U4)> Public BytesReturned As Int32
<MarshalAs(UnmanagedType.U4)> Public Status As Int32
Public Sub New(ByVal URL As String, ByVal BufferLength As Integer)
Me.URL = URL
Me.BuffPtr = Marshal.AllocHGlobal( _
Marshal.SizeOf(GetType(Byte))*BufferLength)
End Sub
End Structure

Then use Marshal.Copy to retrieve the byte array:
Dim buff( ) As Byte

Marshal.Copy(MyBuffers(0).BuffPtr, buff, 0, MyBuffers(0).BufferLength)

hope that helps..
Imran.
 
G

Guest

GREAT!!!

That worked...

I added a readonly property to return the byte array. I had to deminsion
the byte array before the copy, and I went ahead and released the memory
allocated for the buffer after copying the array.

Thanks very much for your help Imran.

Mick
 
I

Imran Koradia

That worked...

cool !
I added a readonly property to return the byte array. I had to deminsion
the byte array before the copy, and I went ahead and released the memory
allocated for the buffer after copying the array.

yup - I forgot the minute details ;-) But surely you do need to release the
unmanaged memory since the GC isn't aware of unmanaged allocations.
Thanks very much for your help Imran.

glad to help :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top