marshaling problem with struct converted from C

A

Angel

I converted a C-style struct into C# but when I call a dll function with it
as parm, I get "can not marshal field stack of type ZM7.ZIP4_PARM: This type
can not be marshaled as a structure field.". The dll function was working
great at accessing and reading the struct but then I noticed that I had
commented the member of type struct (trying to run another dll function that
writes to that struct member). When I uncomment it, I get the error.

This is what the struct looks like:


public class ZIP4_PARM
{
public ZIP4_PARM()
{
}

[MarshalAs( UnmanagedType.ByValTStr, SizeConst=51 )]
public string iadl2;
public short respn;
public char retcc;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=86 )]
public string rsvd2;
public footer foot;

public struct footer
{
public char a;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6)]
public string rsvd3;
}

[MarshalAs(UnmanagedType.ByValArray, SizeConst=10)] //I commented but
then uncommented because function writes to this
public ADDR_REC[] stack;
//I commented but then uncommented because function writes to this
}

public struct ADDR_REC
{
char detail_code;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6 )]
public string zip_code;
char action_code;
char sec_code;
}
 
W

Willy Denoyette [MVP]

The interop marshaler cannot handle this, you will have to do some
marshaling yourself using Marshal.Copy, Marshal.StructureToPtr and
Marshal.PtrToStructure methods.

Essential thing is that you replace this
[MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]
public ADDR_REC[] stack;
by
[MarshalAs(UnmanagedType.ByValArray, SizeConst=90)]
public byte[] stack;
and that you correctly set the attributes for the marshaler and do the
necessary casting :).


Following piece of code partly illustrates how you can handle this.
Note: I assume the char are 8 bit (ansi).

using System;
using System.Runtime.InteropServices;

class Tester
{
static void Main()
{
ZIP4_PARM z = new ZIP4_PARM();
// create an ADDR_REC struct
ADDR_REC ar = new ADDR_REC();
ar.detail_code =1;
ar.zip_code = "B_9000";
ar.action_code = (byte)'x';
ar.sec_code = (byte)'y';
// allocate a byte array large enough to hold 10 structs ADDR_REC and set
ref into z struct.stack
z.stack = new byte[90]; // hard coded length of 10 array's of struct
ADDR_REC in bytes
// allocate unmanaged buffer of size ADDR_REC
IntPtr tu = Marshal.AllocHGlobal( Marshal.SizeOf(ar));
// copy managed struct to unmanaged buffer
Marshal.StructureToPtr(ar, tu, true);
// copy unmanaged buffer to managed array
Marshal.Copy(tu, z.stack, 0, 9);
for (int i = 0 ;i < 9 ;i++ )
Console.Write("{0:x2} ",(byte)z.stack);
//
// Do the reverse when unmarshaling array z.stack....
.....
}
}
// !!! Ansi char's
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct ZIP4_PARM
{
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=51 )]
public string iadl2;
public short respn;
public char retcc;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=86 )]
public string rsvd2;
public footer foot;
public struct footer
{
public char a;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6)]
public string rsvd3;
}
[MarshalAs(UnmanagedType.ByValArray, SizeConst=90)]
public byte[] stack; // SizeConst = 10 * size of unmanaged struct
ADDR_REC
}
// Ansi !!!!
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct ADDR_REC
{
internal byte detail_code;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6 )]
public string zip_code;
internal byte action_code;
internal byte sec_code;
}
 
A

Angel

The inner structure is only accesssed (and filled) by proprietary functions.
I only need to modify the actual struct so that when I send it, the function
can read and modify it.

Would this code still apply?

Angel



Willy Denoyette said:
The interop marshaler cannot handle this, you will have to do some
marshaling yourself using Marshal.Copy, Marshal.StructureToPtr and
Marshal.PtrToStructure methods.

Essential thing is that you replace this
[MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]
public ADDR_REC[] stack;
by
[MarshalAs(UnmanagedType.ByValArray, SizeConst=90)]
public byte[] stack;
and that you correctly set the attributes for the marshaler and do the
necessary casting :).


Following piece of code partly illustrates how you can handle this.
Note: I assume the char are 8 bit (ansi).

using System;
using System.Runtime.InteropServices;

class Tester
{
static void Main()
{
ZIP4_PARM z = new ZIP4_PARM();
// create an ADDR_REC struct
ADDR_REC ar = new ADDR_REC();
ar.detail_code =1;
ar.zip_code = "B_9000";
ar.action_code = (byte)'x';
ar.sec_code = (byte)'y';
// allocate a byte array large enough to hold 10 structs ADDR_REC and set
ref into z struct.stack
z.stack = new byte[90]; // hard coded length of 10 array's of struct
ADDR_REC in bytes
// allocate unmanaged buffer of size ADDR_REC
IntPtr tu = Marshal.AllocHGlobal( Marshal.SizeOf(ar));
// copy managed struct to unmanaged buffer
Marshal.StructureToPtr(ar, tu, true);
// copy unmanaged buffer to managed array
Marshal.Copy(tu, z.stack, 0, 9);
for (int i = 0 ;i < 9 ;i++ )
Console.Write("{0:x2} ",(byte)z.stack);
//
// Do the reverse when unmarshaling array z.stack....
....
}
}
// !!! Ansi char's
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct ZIP4_PARM
{
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=51 )]
public string iadl2;
public short respn;
public char retcc;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=86 )]
public string rsvd2;
public footer foot;
public struct footer
{
public char a;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6)]
public string rsvd3;
}
[MarshalAs(UnmanagedType.ByValArray, SizeConst=90)]
public byte[] stack; // SizeConst = 10 * size of unmanaged struct
ADDR_REC
}
// Ansi !!!!
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct ADDR_REC
{
internal byte detail_code;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6 )]
public string zip_code;
internal byte action_code;
internal byte sec_code;
}


Angel said:
I converted a C-style struct into C# but when I call a dll function with it
as parm, I get "can not marshal field stack of type ZM7.ZIP4_PARM: This
type
can not be marshaled as a structure field.". The dll function was working
great at accessing and reading the struct but then I noticed that I had
commented the member of type struct (trying to run another dll function
that
writes to that struct member). When I uncomment it, I get the error.
 
W

Willy Denoyette [MVP]

Angel said:
The inner structure is only accesssed (and filled) by proprietary
functions.
I only need to modify the actual struct so that when I send it, the
function
can read and modify it.

Would this code still apply?


I can't answer this without knowing what the function expects as argument.
Could you post your C function signature?

Willy.
 
A

Angel

The original function syntax is:

int z4adrinq(ZIP4_PARM *parm);

We only read from the ZIP4_PARM . z4adrinq function writes to the struct. I
only need to convert the struct into something the function can read/write
when invoked from C#. The function only generates a run-time error when
ADDR_REC stack[10]; is included. Otherwise, it works great.

The original struct looked like this (I eliminated any unnecessary members):

typedef struct
{
char rsvd0[4];
struct { //embedded struct
char a;
} foot;
ADDR_REC stack[10]; //if I comment this, it works. Unfortunately,
some other functions with parm (ZIP4_PARM *parm) use this part.
} ZIP4_PARM;

typedef struct
{
char detail_code;
} ADDR_REC;

And thanks for helping me out with this. It's been a real pain in the ...

Angel
 
W

Willy Denoyette [MVP]

Angel said:
The original function syntax is:

int z4adrinq(ZIP4_PARM *parm);

We only read from the ZIP4_PARM . z4adrinq function writes to the struct.
I
only need to convert the struct into something the function can read/write
when invoked from C#. The function only generates a run-time error when
ADDR_REC stack[10]; is included. Otherwise, it works great.

The original struct looked like this (I eliminated any unnecessary
members):

typedef struct
{
char rsvd0[4];
struct { //embedded struct
char a;
} foot;
ADDR_REC stack[10]; //if I comment this, it works. Unfortunately,
some other functions with parm (ZIP4_PARM *parm) use this part.
} ZIP4_PARM;

typedef struct
{
char detail_code;
} ADDR_REC;

And thanks for helping me out with this. It's been a real pain in the ...

Angel

Yes, you should pass a pointer to a unmanaged buffer to the C function, on
return you should marshal the ADDR_REC array to an array of ADDR_REC
objects.

Following should get you a started.
Note: I changed the structs to classes because PtrToStructure needs an
object on the heap.

using System;
using System.Runtime.InteropServices;

class Tester
{
static void Main()
{
int arElements = 10;
ZIP4_PARM z = new ZIP4_PARM();
ADDR_REC ar = new ADDR_REC();
ADDR_REC[] arrrayar = new ADDR_REC[arElements];
int arBytes = Marshal.SizeOf(ar);
IntPtr ptrz = Marshal.AllocHGlobal( Marshal.SizeOf(z));
IntPtr ptra = Marshal.AllocHGlobal(arBytes);
int z4adrinq(ZIP4_PARM *parm);
Marshal.PtrToStructure(ptrz, z);
for (int i = 0; i < arElements; i++ )
{
int displ = i * arBytes;
Marshal.Copy(z.stack, displ , ptra, arBytes);
Marshal.PtrToStructure(ptra, ar);
arrrayar = ar;
}
Marshal.FreeHGlobal(ptrz);
Marshal.FreeHGlobal(ptra);
}
}
// struct changed into class !!!!!!
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public class ZIP4_PARM
{
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=51 )]
public string iadl2;
public short respn;
public char retcc;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=86 )]
public string rsvd2;
public footer foot;
public struct footer
{
public char a;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6)]
public string rsvd3;
}
[MarshalAs(UnmanagedType.ByValArray, SizeConst=90)]
public char[] stack;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public class ADDR_REC
{
internal char detail_code;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6 )]
public string zip_code;
internal char action_code;
internal char sec_code;
}
}
 
A

Angel

Thanks for the help and Sorry for repeating the post. I've been looking at
this marshallimg code for days and I just get kinda frustrated when it
doesn't run like you want it to. And the client is also waiting for results
so it gets even more frustrating.
At least now I get a different new error so I guess I that's good... I'm
receiving "Requested range extends past the end of the array." with
instruction Marshal.PtrToStructure(ptra, ar) on the first run of the For
loop . Was the example you wrote dependent on the sizes of the structs? The
original structs are much bigger (it has many more of the string, int, and
char members). But it only has one member pointing to the embedded struct.

Thanks again for your help.
Angel


using System;
using System.Runtime.InteropServices;

class Tester
{
static void Main()
{
int arElements = 10;
ZIP4_PARM z = new ZIP4_PARM();
ADDR_REC ar = new ADDR_REC();
ADDR_REC[] arrrayar = new ADDR_REC[arElements];
int arBytes = Marshal.SizeOf(ar);
I ntPtr ptrz = Marshal.AllocHGlobal( Marshal.SizeOf(z));
IntPtr ptra = Marshal.AllocHGlobal(arBytes);
int z4adrinq(ZIP4_PARM *parm);
Marshal.PtrToStructure(ptrz, z);
for (int i = 0; i < arElements; i++ )
{
int displ = i * arBytes;
Marshal.Copy(z.stack, displ , ptra, arBytes);
Marshal.PtrToStructure(ptra, ar); //Requested range extends
past the end of the array
arrrayar = ar;
}
Marshal.FreeHGlobal(ptrz);
Marshal.FreeHGlobal(ptra);
}
}
// struct changed into class !!!!!!
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public class ZIP4_PARM
{
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=51 )]
public string iadl2;
public short respn;
public char retcc;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=86 )]
public string rsvd2;
public footer foot;
public struct footer
{
public char a;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6)]
public string rsvd3;
}
[MarshalAs(UnmanagedType.ByValArray, SizeConst=90)]
public char[] stack;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public class ADDR_REC
{
internal char detail_code;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6 )]
public string zip_code;
internal char action_code;
internal char sec_code;
}
}






Willy Denoyette said:
Angel said:
The original function syntax is:

int z4adrinq(ZIP4_PARM *parm);

We only read from the ZIP4_PARM . z4adrinq function writes to the struct.
I
only need to convert the struct into something the function can read/write
when invoked from C#. The function only generates a run-time error when
ADDR_REC stack[10]; is included. Otherwise, it works great.

The original struct looked like this (I eliminated any unnecessary
members):

typedef struct
{
char rsvd0[4];
struct { //embedded struct
char a;
} foot;
ADDR_REC stack[10]; //if I comment this, it works. Unfortunately,
some other functions with parm (ZIP4_PARM *parm) use this part.
} ZIP4_PARM;

typedef struct
{
char detail_code;
} ADDR_REC;

And thanks for helping me out with this. It's been a real pain in the ....

Angel

Yes, you should pass a pointer to a unmanaged buffer to the C function, on
return you should marshal the ADDR_REC array to an array of ADDR_REC
objects.

Following should get you a started.
Note: I changed the structs to classes because PtrToStructure needs an
object on the heap.

using System;
using System.Runtime.InteropServices;

class Tester
{
static void Main()
{
int arElements = 10;
ZIP4_PARM z = new ZIP4_PARM();
ADDR_REC ar = new ADDR_REC();
ADDR_REC[] arrrayar = new ADDR_REC[arElements];
int arBytes = Marshal.SizeOf(ar);
IntPtr ptrz = Marshal.AllocHGlobal( Marshal.SizeOf(z));
IntPtr ptra = Marshal.AllocHGlobal(arBytes);
int z4adrinq(ZIP4_PARM *parm);
Marshal.PtrToStructure(ptrz, z);
for (int i = 0; i < arElements; i++ )
{
int displ = i * arBytes;
Marshal.Copy(z.stack, displ , ptra, arBytes);
Marshal.PtrToStructure(ptra, ar);
arrrayar = ar;
}
Marshal.FreeHGlobal(ptrz);
Marshal.FreeHGlobal(ptra);
}
}
// struct changed into class !!!!!!
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public class ZIP4_PARM
{
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=51 )]
public string iadl2;
public short respn;
public char retcc;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=86 )]
public string rsvd2;
public footer foot;
public struct footer
{
public char a;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6)]
public string rsvd3;
}
[MarshalAs(UnmanagedType.ByValArray, SizeConst=90)]
public char[] stack;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public class ADDR_REC
{
internal char detail_code;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6 )]
public string zip_code;
internal char action_code;
internal char sec_code;
}
}
 
A

Angel

Also, ADDR_REC has more than one member. It contains several char members.


Willy Denoyette said:
Angel said:
The original function syntax is:

int z4adrinq(ZIP4_PARM *parm);

We only read from the ZIP4_PARM . z4adrinq function writes to the struct.
I
only need to convert the struct into something the function can read/write
when invoked from C#. The function only generates a run-time error when
ADDR_REC stack[10]; is included. Otherwise, it works great.

The original struct looked like this (I eliminated any unnecessary
members):

typedef struct
{
char rsvd0[4];
struct { //embedded struct
char a;
} foot;
ADDR_REC stack[10]; //if I comment this, it works. Unfortunately,
some other functions with parm (ZIP4_PARM *parm) use this part.
} ZIP4_PARM;

typedef struct
{
char detail_code;
} ADDR_REC;

And thanks for helping me out with this. It's been a real pain in the ....

Angel

Yes, you should pass a pointer to a unmanaged buffer to the C function, on
return you should marshal the ADDR_REC array to an array of ADDR_REC
objects.

Following should get you a started.
Note: I changed the structs to classes because PtrToStructure needs an
object on the heap.

using System;
using System.Runtime.InteropServices;

class Tester
{
static void Main()
{
int arElements = 10;
ZIP4_PARM z = new ZIP4_PARM();
ADDR_REC ar = new ADDR_REC();
ADDR_REC[] arrrayar = new ADDR_REC[arElements];
int arBytes = Marshal.SizeOf(ar);
IntPtr ptrz = Marshal.AllocHGlobal( Marshal.SizeOf(z));
IntPtr ptra = Marshal.AllocHGlobal(arBytes);
int z4adrinq(ZIP4_PARM *parm);
Marshal.PtrToStructure(ptrz, z);
for (int i = 0; i < arElements; i++ )
{
int displ = i * arBytes;
Marshal.Copy(z.stack, displ , ptra, arBytes);
Marshal.PtrToStructure(ptra, ar);
arrrayar = ar;
}
Marshal.FreeHGlobal(ptrz);
Marshal.FreeHGlobal(ptra);
}
}
// struct changed into class !!!!!!
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public class ZIP4_PARM
{
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=51 )]
public string iadl2;
public short respn;
public char retcc;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=86 )]
public string rsvd2;
public footer foot;
public struct footer
{
public char a;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6)]
public string rsvd3;
}
[MarshalAs(UnmanagedType.ByValArray, SizeConst=90)]
public char[] stack;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public class ADDR_REC
{
internal char detail_code;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=6 )]
public string zip_code;
internal char action_code;
internal char sec_code;
}
}
 
A

Angel

In the code you sent me, I noticed that you changed the array of structs to
public char[] stack because you thought the substructure only contained one
variable (that was my fault). But the struct I'm changing to C# really looks
like this :

typedef struct
{
char detail_code;
char zip_code[5+1];
char update_key[10+1];
char action_code;
char rec_type;
char carr_rt[4+1];
} ADDR_REC;

typedef struct
{
long relver;
char iadl1[50+1];
char iadl2[50+1];
char ictyi[50+1];
char istai[2+1];
struct {
char a;
char b;
char c;
}
ADDR_REC stack[10];
char rsvd4[194];
} ZIP4_PARM;

Angel
 

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