array of structures to unmanaged memory

D

DLI

I tried a similar post in interop and got a blank stare.

Can ayone tell me how to pass an array of structures which contain arrays of
blittable types to unmanaged memory?

This syntax does not work:
Public Declare Sub SomeDll Lib "SomeDll.dll" ( ByRef MyStructure() as
SomeStructure, ByRef MyOutput as Int32)

I get junk on the dll side.

If I try to decorate the definition like this:
Public Declare Sub SomeDll Lib "SomeDll.dll" (
<MarshalAs.UnmanagedType,SafeArray>ByRef MyStructure() as SomeStructure,
ByRef MyOutput as Int32)

I get an exception the Type is not valid.

The structure has a definition similar to

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi, pack:=8)> Public
Structure SomeStructure

<MarshalAs(UnmanagedType.ByValArray, SizeConst:=8)> Dim GasID() As Byte

Dim Mw As Double

<MarshalAs(UnmanagedType.ByValArray, SizeConst:=4)> Dim CpPar() As Double '

<MarshalAs(UnmanagedType.ByValArray, SizeConst:=2)> Dim MuPar() As Double

<MarshalAs(UnmanagedType.ByValArray, SizeConst:=3)> Dim LmdaPar() As Double

End Structure

Thanks in advance!!
 
J

Jeroen Mostert

DLI said:
I tried a similar post in interop and got a blank stare.
It's not a busy group, and interop is tricky business.
Can ayone tell me how to pass an array of structures which contain arrays of
blittable types to unmanaged memory?

This syntax does not work:
Public Declare Sub SomeDll Lib "SomeDll.dll" ( ByRef MyStructure() as
SomeStructure, ByRef MyOutput as Int32)
Try passing the array as ByVal. I don't have a copy of VB handy, but if I'm
not mistaken, "ByRef" means you want to pass a *pointer* to the array, which
isn't likely to be correct. If you just want the array to be modifiable on
the unmanaged side, use <In, Out> instead, so the marshaller will copy the
array back to managed code.

If that doesn't work, please give the prototype of the unmanaged function.
That rather matters.
 
D

DLI

Thank you for the reply.

I am trying to port some VB6 code that does work properly with this dll
The VB6 code passed in the array of Types ByRef
I tried ByVal and I get a stack overflow error

In regards to this posting, I did google this thread:

http://www.dotnet247.com/247reference/msgs/43/215864.aspx

The following quote is from that very long thread
<<<<
The marshaller cannot handle "embedded arrays of structures" and "variable
length structs". It can only handle embedded arrays of blittable types(int,
char etc) - as I illustrated in a previous post.
If you really want to use C# to pass such managed structures to unmanaged
code, you'll need to help the marshaller by passing as argument to the API,
a pointer to a buffer, that contains a copy of the managed struct fields you
marshalled using the Marshal.WriteXX methods. On return you need to copy the
buffer contents back to the managed structure using the Marshal.ReadXX
I am just surprised that I can't find a syntax in DotNet to marshal an array
of structures
the same way VB6 marshalled an array of types.

The un-managed function is buried in a Fortran dll that I do not have the
source for.

Am I missing something simple here?
 
J

Jeroen Mostert

DLI said:
I am trying to port some VB6 code that does work properly with this dll
The VB6 code passed in the array of Types ByRef

Alright, then, post the VB6 imports and structure definition, and possibly a
small example of use. This should be of some help. Note in particular that
"VB6 used ByRef" does *not* mean VB.NET will use ByRef the same way. .NET
marshalling is in most cases quite different from what VB6 did.

If you say VB6, I'm thinking that your function wants SAFEARRAYs, not
regular old arrays. Post the VB6 declarations and we can probably clear this up.
 
D

DLI

Here is a more detailed structure example VB6 code first:
note the first two Types are passed in as Types and Not arrays of Type
Gases is an array of Type GasKind
My Dot Net code works fine on ProbParamKind and RunOptKind
It populates the first element of the Gases Array properly
The rest of the elements are corrupted

Public Type ProbParamKind
Na As Long
Nc As Long
Nstep As Long
MaxCycles As Long
AxialPts As Long
End Type


Public Type RunOptKind
KeyProdID As Long
KeyImpID As Long
KeyProdSpec As Double
KeyImpSpec As Double
RecoveryTarget As Double
PurgeFactor As Double
CoCurCutOff As Double
BedSizeFactor As Double
BedVol As Double
BedLen As Double
TempWall As Double
NonAdiabaticFrac As Double
ISOmodel(1 To 8) As Byte 'Array of bytes representing IsoModel As String *
8
End Type


Public Type GasKind
GasID(1 To 8) As Byte 'Array of bytes representing GasID As String * 8
Mw As Double
CpPar(1 To 4) As Double
MuPar(1 To 2) As Double
LmdaPar(1 To 3) As Double
End Type

It makes a call to a fortran dll using arrays of these types and a long to
return an error code.

Public Declare Sub PSAEngine Lib "PSAxEngine.dll" ( _
ProbParam As ProbParamKind, RunOpt As RunOptKind, _
Gases As GasKind, ByRef errFlag As Long)

Here is the call in the code
Call PSAEngine( _
ProbParam, ProbOpt, RunOpt, Gases(1), errFlag)


It works fine.

I have defined these structures in VB DotNet that match the types in VBA

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Public
Structure ProbParamKind
Dim Na As Int32
Dim Nc As Int32
Dim Nstep As Int32
Dim MaxCycles As Int32
Dim AxialPts As Int32
End Structure

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Public
Structure RunOptKind
Dim KeyProdID As Int32
Dim KeyImpID As Int32
Dim KeyProdSpec As Double
Dim KeyImpSpec As Double
Dim RecoveryTarget As Double
Dim PurgeFactor As Double
Dim CoCurCutOff As Double
Dim BedSizeFactor As Double
Dim BedVol As Double
Dim BedLen As Double
Dim TempWall As Double
Dim NonAdiabaticFrac As Double
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=8)> Dim ISOmodel()
As Byte

End Structure

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Public
Structure GasKind
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=8)> Dim GasID() As Byte
Dim Mw As Double
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=4)> Dim CpPar() As
Double
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=2)> Dim MuPar() As
Double
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=3)> Dim LmdaPar() As
Double
End Structure

Here is the dotnet call definition

Public Declare Sub PSAEngine Lib "PSAxEngine.dll" ( _
ByRef ProbParam As ProbParamKind, ByRef RunOpt As RunOptKind, _
ByRef Gases As GasKind, ByRef errFlag As Int32)

Here is the call in code

Call PSAEngine(ProbParam, RunOpt, Gases(0), errFlag)

When I try to use the same fortran dll with matching arrays, I get junk
after the first element of the array of GasKind.


If I pass in Gases(1) , the dll gets the proper data for the second element
of the Gases array and junk for the rest so it appears that the marshaller
is just getting one element of the array correct.
 
D

DLI

BTW, Thank you again for looking at this. I know how tough it is to
troubleshoot without the actual application if front of you.
 
J

Jeroen Mostert

DLI said:
Here is a more detailed structure example VB6 code first:
note the first two Types are passed in as Types and Not arrays of Type
Gases is an array of Type GasKind

How many elements? Does it expect a particular size or does it somehow work
with any number? It should have some way of telling the function how many
gases are in there, otherwise I don't see how the Fortran code knows where
to stop.
It makes a call to a fortran dll using arrays of these types and a long to
return an error code.

Public Declare Sub PSAEngine Lib "PSAxEngine.dll" ( _
ProbParam As ProbParamKind, RunOpt As RunOptKind, _
Gases As GasKind, ByRef errFlag As Long)
Those are not arrays! GasKind is a structure containing arrays, but the
values themselves are not arrays.
Here is the call in the code
Call PSAEngine( _
ProbParam, ProbOpt, RunOpt, Gases(1), errFlag)
I see what your code is doing. It's using a nasty little trick: it's passing
an unmanaged array by passing a reference to its first element. In the
unmanaged world, an array *is* a pointer to its first element, which is why
this works. And this is the only way you can do it in VB 6 (because VB 6
passes its own array types as SAFEARRAYs, which the Fortran DLL is not
expecting).

The reason this doesn't work in .NET is as simple: you're telling the
marshaller that you only want to marshal one element. So it does that! The
function is expecting an entire array there, but the array just isn't there
because the marshaller hasn't copied it.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Public

Not really an error, but if your structures do not contain any string types,
specifying the CharSet is meaningless and it can be omitted.
Here is the dotnet call definition

Public Declare Sub PSAEngine Lib "PSAxEngine.dll" ( _
ByRef ProbParam As ProbParamKind, ByRef RunOpt As RunOptKind, _
ByRef Gases As GasKind, ByRef errFlag As Int32)
If you know that "Gases" always has a particular size, this should work:

Public Declare Sub PSAEngine Lib "PSAxEngine.dll" ( _
ByRef ProbParam As ProbParamKind, ByRef RunOpt As RunOptKind, _
<MarshalAs(UnmanagedType.LPArray, SizeConst:=[size]> _
Gases() As GasKind, ByRef errFlag As Int32)

Call it by passing the entire array, not an element.

If you don't know the size of "Gases" at compile time, you have to marshal
the array manually, because there's no way the marshaller can know what to
copy. Here's the declaration for that:

Public Declare Sub PSAEngine Lib "PSAxEngine.dll" ( _
ByRef ProbParam As ProbParamKind, ByRef RunOpt As RunOptKind, _
Gases As IntPtr, ByRef errFlag As Int32)

To call that, you need some boilerplate you'll probably want to wrap in a
different function. Beware, I have no VB compiler handy and it's been a long
time since I wrote VB, so the following code might contain errors, but you
should get the jist of it:

' I'll assume the array is in the variable "Gases"
Dim GasesBuffer As IntPtr = IntPtr.Zero
Dim GasKindSize As Int = Marshal.SizeOf(GetType(GasKind))
Try
' Allocate enough unmanaged memory to copy the array into
GasesBuffer = Marshal.AllocHGlobal(GasKindSize * Gases.Length)
' Copy the managed array to that block of memory element-by-element
Dim I As Integer
For I := 0 To Gases.Length
Marshal.StructureToPtr(Gases(I), CType(CLong(GasesBuffer) + I *
GasKindSize), IntPtr), False)
Next
Call PSAEngine(ProbParam, RunOpt, GasesBuffer, errFlag)
Finally
If GasesBuffer <> IntPtr.Zero Then Marshal.FreeHGlobal(GasesBuffer)
End Try

For this to work it's critical that the array elements are properly aligned.
You may need to adjust the Pack attribute of the struct.
 
D

DLI

<<How many elements? Does it expect a particular size or does it somehow
work
with any number? It should have some way of telling the function how many
gases are in there, otherwise I don't see how the Fortran code knows where
to stop.>>


fixed length of ten elements


Thanks.

I will try this syntax. I did try something very similar but had no success.
If I get it right, I will be sure to post the solution for future victims.

I agree about the dirty trick of passing the first array element. I
inherited the VB6 code from an evil programmer.
 
D

DLI

Jeroen, You are a genius!!
Thank you, thank you!

The following worked.
Public Declare Sub PSAEngine Lib "PSAxEngine.dll" ( _

<MarshalAs(UnmanagedType.Struct)> ByRef ProbParam As ProbParamKind,
<MarshalAs(UnmanagedType.Struct)> ByRef ProbOpt As ProbOptKind,
<MarshalAs(UnmanagedType.Struct)> ByRef RunOpt As RunOptKind,
<MarshalAs(UnmanagedType.LPArray, SizeConst:=10)> ByVal Gases() As GasKind,
ByRef errFlag As Int32)

Now, If I may impose on you with one last question: I could not pass the
array of Gases in ByRef as I get a compile error that states it is not
allowed. If I need to see changes to the array after the dll returns it,
will I still see them or do I need to do something else? I have other calls
that need to see changes in structures from un-managed code.
 
J

Jeroen Mostert

DLI said:
Jeroen, You are a genius!!

Oh, I'm too modest to admit it.

But now that you've seen this, you know how to solve this problem in the
future too, so now you can spread the genius virus around.
Thank you, thank you!

The following worked.
Public Declare Sub PSAEngine Lib "PSAxEngine.dll" ( _

<MarshalAs(UnmanagedType.Struct)> ByRef ProbParam As ProbParamKind,
<MarshalAs(UnmanagedType.Struct)> ByRef ProbOpt As ProbOptKind,
<MarshalAs(UnmanagedType.Struct)> ByRef RunOpt As RunOptKind,
<MarshalAs(UnmanagedType.LPArray, SizeConst:=10)> ByVal Gases() As GasKind,
ByRef errFlag As Int32)

Now, If I may impose on you with one last question: I could not pass the
array of Gases in ByRef as I get a compile error that states it is not
allowed. If I need to see changes to the array after the dll returns it,
will I still see them or do I need to do something else? I have other calls
that need to see changes in structures from un-managed code.
Change the declaration to this:

Public Declare Sub PSAEngine Lib "PSAxEngine.dll" ( _

<MarshalAs(UnmanagedType.Struct)> ByRef ProbParam As ProbParamKind,
<MarshalAs(UnmanagedType.Struct)> ByRef ProbOpt As ProbOptKind,
<MarshalAs(UnmanagedType.Struct)> ByRef RunOpt As RunOptKind,
<MarshalAs(UnmanagedType.LPArray, SizeConst:=10), In, Out> ByVal Gases() As
GasKind, ByRef errFlag As Int32)

The "In, Out" attributes tell the marshaller to both copy the array from
managed to unmanaged and back again after the function call. Specifying only
"Out" would mean that the unmanaged code fills the array and the input is
not important. The default, of course, is just "In".
 

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