views:

786

answers:

2

Hi, I have to create an array of structure type in VB.net. but I am getting error while marshalling this error. I have to pass this array of structure type in to Dll function.

Code: Structure declaration:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
    Public Structure dx_entry
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=10)> _
        Public dx As String
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=3)> _
        Public type As String
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=1)> _
        Public narray As String
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=1)> _
        Public ctier As String
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=1)> _
        Public poa As String
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=1)> _
        Public poa_rsvd As String
       <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=81)> _
        Public filler As String
    End Structure

Code of initiallization and marshalling:

Dim stpDx(2) As dx_entry
stpDx(1).dx = "5939" & Space(6)
        stpDx(1).type = "BK" & Space(1)
        stpDx(1).narray = Space(1)
        stpDx(1).ctier = Space(1)
        stpDx(1).poa = "Y"
        stpDx(1).poa_rsvd = Space(1)
        stpDx(1).filler = Space(81)
        stpDx(2).dx = "1231" & Space(6)
        stpDx(2).type = "BF" & Space(1)
        stpDx(2).narray = Space(1)
        stpDx(2).ctier = Space(1)
        stpDx(2).poa = "Y"
        stpDx(2).poa_rsvd = Space(1)
        stpDx(2).filler = Space(81)
        Dim pDxBuf As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(stpDx))
        Marshal.StructureToPtr(stpDx, pDxBuf, False)
        ezg_Block.pDx = pDxBuf

I am getting the following error:

An unhandled exception of type 'System.ArgumentException' occurred in Audit_Demo_2307.exe

Additional information: Type dx_entry[] can not be marshaled as an unmanaged structure; no meaningful size or offset can be computed.

+1  A: 

I looked in to this a little bit, and the first issue seems that you pass an array into the Marshal.SizeOf method (it is this call that throws the exception). Since all members of your structure have a fixed size, you know that all instances of that type will have the same size. So, you can instead ask Marshal.SizeOf to return the size for one such instance:

Dim structSize As Integer = Marshal.SizeOf(GetType(dx_entry))

The next thing is that Marshal.StructureToPtr copies the data from a structure, not an array of structures. So you will need some other way of copying your structure array to the allocated memory. There is no method that will do this for you in one call, but what you can do is to copy each of the structure instances in your array into a byte array (using the Marshal.Copy method), and then copying that byte array to the memory pointed to by the pointer.

This is easiest to do as a two step rocket. First, let's make a method that converts the array of structures to a byte array:

Private Shared Function GetByteArray(ByVal array As dx_entry()) As Byte()
    Dim structSize As Integer = Marshal.SizeOf(GetType(dx_entry))
    Dim size As Integer = structSize * array.Length
    Dim dataBuffer As Byte() = New Byte(size - 1) {}
    For i As Integer = 0 To array.Length - 1
        Dim pTemp As IntPtr = Marshal.AllocHGlobal(structSize)
        Marshal.StructureToPtr(array(i), pTemp, True)
        Marshal.Copy(pTemp, dataBuffer, 0 + (i * structSize), structSize)
        Marshal.FreeHGlobal(pTemp)
    Next
    Return dataBuffer
End Function

Second, use the GetByteArray method and copy the returned byte array to the memory pointed to by the pointer:

Dim data As Byte() = GetByteArray(stpDx)
Dim pDxBuf As IntPtr = Marshal.AllocHGlobal(data.Length)
Marshal.Copy(data, 0, pDxBuf, data.Length)
ezg_Block.pDx = pDxBuf

I hope that does what you are looking for...

As a side note; since you in your structure have specified the fixed size for each of the fields, you don't need to pad the values with spaces in your code; this is taken care of by the framework when marshalling your data.

Update

To read the data back, you basically need to do the same thing, but backwards:

Private Shared Function GetStructArray(ByVal dataBuffer() As Byte) As dx_entry()
    Dim structSize As Integer = Marshal.SizeOf(GetType(dx_entry))
    If dataBuffer.Length Mod structSize <> 0 Then
        Throw New ArgumentException("Argument is of wrong length", "dataBuffer")
    End If
    Dim elementCount As Integer = Convert.ToInt32(dataBuffer.Length / structSize)
    Dim size As Integer = structSize * elementCount
    Dim result() As dx_entry = New dx_entry(elementCount - 1) {}
    For i As Integer = 0 To elementCount - 1
        Dim pTemp As IntPtr = Marshal.AllocHGlobal(structSize)
        Marshal.Copy(dataBuffer, 0 + (i * structSize), pTemp, structSize)
        result(i) = DirectCast(Marshal.PtrToStructure(pTemp, GetType(dx_entry)), dx_entry)
        Marshal.FreeHGlobal(pTemp)
    Next
    Return result
End Function

Called like that:

Dim structSize As Integer = Marshal.SizeOf(GetType(dx_entry))        
Dim newdata() As Byte = New Byte(structSize * numberOfElements -1) {}
Marshal.Copy(ezg_Block.pDx, newdata, 0, newdata.Length)
Dim newstruct() As dx_entry = GetStructArray(data)

Note: To get all this to work properly, you will need to tweak the structure a bit: it seems you need to increase the SizeConst by 1. This is since the strings are null-terminated, so there need to be a position for the null byte as well:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Public Structure dx_entry
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=11)> _
    Public dx As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=4)> _
    Public type As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=2)> _
    Public narray As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=2)> _
    Public ctier As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=2)> _
    Public poa As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=2)> _
    Public poa_rsvd As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=82)> _
     Public filler As String
End Structure
Fredrik Mörk
Thanks alot Fredrik,Please let me know, if I have to retreive the data after the Dll function call, how I have to go ahead to do this.Actually there are some values in this structure those will get upadated after the function call.
Yogi
I heartly appreciate your help Fredrik. this solution is working for me. I nedd you help,I have a Union which contains a variable of structure type in C language. I used the following method to marshal it in VB.net<StructLayout(LayoutKind.Explicit)> Public Structure grpr_output_block1<FieldOffset(0)> Public drg As drg_grpr_block1End StructureUsing in code - to pass as pointer in functionDim stpGob1 As grpr_output_block1Dim pGob1Buf As IntPtr=Marshal.AllocHGlobal(Marshal.SizeOfstpGob1)Marshal.StructureToPtr(stpGob1, pGob1Buf, False)Ezg_Block.pGob1=pGob1Bufis it right marshall?
Yogi
A: 

Yogi, I realize this post is over a year old. I do not know how to contact you directly on this forum. I stumbled across this post, and was surprised to see that it appears you are invoking the same thrid-party api dll that I also need to access. As you know, it takes as an argument a very complex structure of dozens of structures (many of which are arrays). It seems you have already coded out the VB.net solution to successfully accessing this dll. Would you be willing to share your code used to implement these strucutres and to make the api call? It would save me lots of time doing from scratch what you have already created.

I'm guessing your code is going to need to change since new regulatory requirements are resulting in many changes to this API's structures, due to be released shortly. If you share the code you did for the current API structures I can modify it for the new structure changes and share that back to you.

I beleive you should be able to contact me vai email as when I signed up for this forum I specified that I accept email from participants.

Thank you.

Russell Oppenheimer