I'm p-invoking into a DLL that returns a void** list of struct pointers, all of the same type. From what I've read, in order to cast and get my structure out of that list, the struct needs to be considered unmanaged. The main culprits to the struct I'm trying to marshal over are the following two fields from the C side:
char name[1024];
int crop[4];
Most guides suggest using string or int[] on the corresponding struct on the managed side, but that having those fields makes it into a managed struct and thus incapable of extracting from the void** list.
What's another way I can marshal these fields that gives me an unmanaged struct?
The structure will marshal without help or need for the unsafe keyword if you declare it like this:
using System.Runtime.InteropServices;
...
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Example {
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
public string name;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
int[] crop;
}
Convert the void* to the structure with Marshal.PtrToStructure().
You can use the fixed keyword to create a buffer with a fixed size array in a data structure:
unsafe struct Foo
{
public fixed byte name[1024];
public fixed int crop[4];
}
static unsafe void DumpCrops(void** ptr, int count)
{
Foo** p = (Foo**)ptr;
for (int i = 0; i < count; i++)
{
Foo* f = p[i];
for (int j = 0; j < 4; j++)
{
Console.WriteLine(f->crop[j]);
}
}
}
You need to add a line in that point on the struct as shown...
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct _FOOBAR {
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 1024, ArraySubType = System.Runtime.InteropServices.UnmanagedType.I2)]
char name[1024];
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = System.Runtime.InteropServices.UnmanagedType.I4)]
int crop[4];
};
You need to double check on the last bit in the attribute bit, UnmanagedType...
Hope that helps,
Best regards,
Tom.
Related
I'm trying to unmarshall an array of variable length of a structure nested inside another structure as in the following code:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CardInfoRequest
{
public ulong CardId;
public byte AppListLength;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct)]
public CardApp[] AppList;
}
[Serializable()]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CardApp
{
public ulong CardAppId;
public byte SomeInformation;
}
And I am unmarshalling it by doing:
var lDataPointer = Marshal.AllocHGlobal(pSize);
Marshal.Copy(pData, 0, lDataPointer, pSize);
var lResult = Marshal.PtrToStructure(lDataPointer, typeof(CardInfoRequest));
Marshal.FreeHGlobal(lDataPointer);
Where pData is a byte array containing the marshalled structure and pSize is its size in runtime (18 when the array has one item, 27 when the array has two items and so forth...).
However, no matter the size in bytes of the stream, whenever I unmarshall it I am always getting AppList.Length == 1.
Can I unmarshall it correctly? Should I do it byte by byte?
Thanks in advance!
The pinvoke marshaller has no idea what size array it needs to create. A fixed-size buffer cannot work either. You have to do it in two steps, first unmarshal the first 2 members, you now know the array size from AppListLength. Create the array, then unmarshal the array elements one by one in a loop.
So roughly (untested)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct CardInfoRequestHeader
{
public ulong CardId;
public byte AppListLength;
}
public struct CardInfoRequest
{
public CardInfoRequestHeader Header;
public CardApp[] AppList;
}
...
var req = new CardInfoRequest();
req.Header = (CardInfoRequestHeader)Marshal.PtrToStructure(pData, typeof(CardInfoRequestHeader));
req.AppList = new CardApp(req.Header.AppListLength);
pData += Marshal.SizeOf(CardInfoRequestHeader);
for (int ix = 0; ix < req.AppList.Length; ++ix) {
req.AppList = (CardInfo)Marshal.PtrToStructure(pData, typeof(CardInfo));
pData += Marshal.SizeOf(CardInfo);
}
Beware the ulong and Pack = 1 are Red Flags, unmanaged data rarely looks like that. The code snippet does make the hard assumption that Pack=1 is accurate.
I have a C++ function in a DLL which takes a pointer to a struct, JPInfo, which in the function is filled with data received from a server, the layout of the C++ struct is as seen below:
typedef struct JP
{
unsigned char type;
DWORD value;
} JP;
typedef struct JPInfo
{
JP jps[3];
_int16 ConT;
_int16 CallT;
unsigned char ret;
unsigned char count;
unsigned char JPOffset;
unsigned char JPPeriod;
} JPInfo;
The function is exported in the DLL like so:
__declspec(dllexport) DWORD __stdcall GetJPInfo(JPInfo* jpi, DWORD time);
The function takes a pointer to a JPInfo struct, I have tried to emulate this struct in C#
[StructLayout(LayoutKind.Sequential, Size = 5), Serializable]
public struct JP
{
byte type;
int value;
}
[StructLayout(LayoutKind.Sequential,Size=23),Serializable]
public struct JPInfo
{
JP[] jps;
Int16 ConT;
Int16 CallT;
byte ret;
byte count;
byte JPOffset;
byte JPPeriod;
}
I attempt to call the function from C# like so:
[DllImport("DLLImp.dll")]
unsafe public static extern int GetJP(ref JPInfo jpi, int time);
// then in main...
JPInfo jpi = new JPInfo;
GetJackpotValues(ref jpi, 4000);
I get an unhandled exception of type "System.ExecutionEngineException". I can't have a fixed size array of JP structs in my JPInfo struct, so I don't know how to approach this.
Thanks.
Have you tried removing your Size attributes on your structs? I haven't had to specify a Size when doing something similar. For your array properties, try attributing them like:
[StructLayout(LayoutKind.Sequential)]
public struct JPInfo
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
JP[] jps;
Int16 ConT;
Int16 CallT;
byte ret;
byte count;
byte JPOffset;
byte JPPeriod;
}
Assuming that the C++ structs are packed, your C# structs should look like this:
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct JP
{
byte type;
uint value;
}
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct JPInfo
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
JP[] jps;
Int16 ConT;
Int16 CallT;
byte ret;
byte count;
byte JPOffset;
byte JPPeriod;
}
On the other hand, if they are not packed then remove the Pack parameter to the StructLayout attribute. You should look for a #pragma pack statement in the C++ header file to understand whether or not the C++ structs are packed.
I'm guessing that the C++ structs are packed because you said that they are mapped onto data received from the server.
Your import should be like so:
[DllImport("DLLImp.dll")]
public static extern uint GetJP(ref JPInfo jpi, uint time);
A DWORD translates to uint rather than int and there is no need for unsafe code here.
With great help of the stackoverflow community, I've managed to call a native DLL function. However, I can't modify the values of ID or intersects array. No matter what I do with it on the DLL side, the old value remains. It seems read-only.
Here are some code fragments:
C++ struct:
typedef struct _Face {
int ID;
int intersects[625];
} Face;
C# mapping:
[StructLayout(LayoutKind.Sequential)]
public struct Face {
public int ID;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 625)]
public int[] intersects;
}
C++ method (type set to DLL in VS2010):
extern "C" int __declspec(dllexport) __stdcall
solve(Face *faces, int n){
for(int i =0; i<n; i++){
for(int r=0; r<625; r++){
faces[i].intersects[r] = 333;
faces[i].ID = 666;
}
}
C# method signature:
[DllImport("lib.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern int solve(Face[] faces, int len);
C# method invocation:
Face[] faces = new Face[10];
faces[0].intersects = new int[625];
faces[0].ID = -1; //.. and add 9 more ..
solve(faces, faces.Length);
// faces[0].ID still equals -1 and not 666
Kindest regards,
e.
You have to tell the pinvoke marshaller explicitly that the array needs to be marshaled back. You do this with the [In] and [Out] attributes. Like this:
[DllImport("...")]
public static extern int solve([In, Out] Face[] faces, int len);
This is an output field only? To get to the bottom of this, I'd try substituting your Face[] parameter with a large-enough a byte[] and see if the byte array gets filled with anything (you'll have to change your [DllExport] a bit).
Also, one other thing I used to experience when doing this with char*'s is that I had to pre-allocate the buffer in C#. For example:
StringBuilder theString=new StringBuilder();
MyUnmanagedFunction(theString);
would not work. But assuming that returned theString was a max 256 characters, I would do this:
StringBuilder theString=new StringBuilder(256);
MyUnmanagedFunction(theString);
And I'd be in business. I'd recommend trying the byte[] substitution, if that doesn't work, try the pre-allocated byte array. Once you are seeing the byte array actually get changed by your C++ code, then you can figure out how to marshal that thing into your C# struct.
I have such a C++ structure:
typedef struct _FILE_OP_BLOCK
{
unsigned short fid; // objective file ID
unsigned short offset; // operating offset
unsigned char len; // buffer length(update)
// read length(read)
unsigned char buff[240];
} FILE_OP_BLOCK;
And now I want to map it in .Net. The tricky thing is that the I should pass a 2 byte array for fid, and integer for len, even though in C# fid is an unsigned short and len is an unsigned char
I wonder whether my structure ( in C#) below is correct?
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Auto)]
public struct File_OP_Block
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public byte[] fid;
public ushort offset;
public byte length;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 240)]
public char[] buff;
}
Your CharSet property on the [DllImport] attribute is definitely wrong, you need CharSet.Ansi to get the P/Invoke marshaller to convert it to a char[]. Declare the buff member as a string for easier usage. While declaring the fid member as a byte[] isn't wrong, I really don't see the point of it. That the unmanaged code copies a char[] into it is an implementation detail. Thus:
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct File_OP_Block
{
public ushort fid;
public ushort offset;
public byte length;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 240)]
public string buff;
}
Given that the C++ short data type is actually two bytes, a two byte array should work. The integer sizes in C/C++ are not strictly defined, so the standard only says that a short is at least two bytes.
The C# char data type is a 16 bit unicode character, so that doesn't match the C++ char data type which is an 8 bit data type. You either need an attribute to specify how the characters are encoded into bytes, or use a byte array.
You might need an attribute to specify the packing, so that there is no padding between the members.
It seems I have yet another problem in the understanding of marshalling to C++ DLL.
Here is the def of the C++ function & struct :
#define SIZE_PLATE (28l)
#define SIZE_HJT (15l)
#define SIZE_DATE (10)
typedef struct _tyrfdePlate
{
TCharA PlateID[SIZE_PLATE];
TInt32 NetworkID;
TInt32 CityID;
TCharA DateS[SIZE_DATE];
TCharA DateE[SIZE_DATE];
TInt32 Width;
TInt32 Height;
TBool Light;
TBool Roll;
TCharA CycleID[4];
TInt16 OrHjt1;
TCharA HJTID1[SIZE_HJT];
TInt16 OrHjt2;
TCharA HJTID2[SIZE_HJT];
TInt16 OrHjt3;
TCharA HJTID3[SIZE_HJT];
TInt16 OrHjt4;
TCharA HJTID4[SIZE_HJT];
} tyrfdePlate;
TInt32 __stdcall tyrfdeSetResults(TInt32 TargetNbr, const TInt32* pTargets, TInt32 PlateNbr, const tyrfdePlate* pPlates);
This is what I made in C# based on a previous question I asked:
[StructLayout(LayoutKind.Sequential, Size = 138), Serializable]
public struct Plate
{
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 28)]
public string PlateID;
public int NetworkID;
public int CityID;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 10)]
public string DateS;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 10)]
public string DateE;
public int Width;
public int Height;
public bool Light;
public bool Roll;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 4)]
public string CycleID;
public short OrHJT1;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 15)]
public string HJTID1;
public short OrHJT2;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 15)]
public string HJTID2;
public short OrHJT3;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 15)]
public string HJTID3;
public short OrHJT4;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 15)]
public string HJTID4;
}
[DllImport("tyrfde.dll", EntryPoint = "tyrfdeSetResults")]
public static extern int SetResults(int targetNbr, [MarshalAs(UnmanagedType.LPArray)] int[] targetIds, int plateNbr, [MarshalAs(UnmanagedType.LPArray)] Plate[] plates);
Here is an example of the call:
List<Plate> plates = new List<Plate>();
plates.Add(new Plate() { PlateID = "56013208", NetworkID = 992038, CityID = 60010, DateS = "01012009", DateE = "31122010", Width = 400, Height = 300, Light = false, Roll = false, CycleID = "0", OrHJT1 = 2, HJTID1 = "579026356", OrHJT2 = 2, HJTID2 = "579026377", OrHJT3 = 2, HJTID3 = "58571903", OrHJT4 = 0, HJTID4 = "0" });
int[] targets = new int[]{1,2,11,12,130};
int result = SetResults(5, targets, 1, plates.ToArray());
Note that I also tried with native Array instead of generic list with same result.
So basically I'm redoing a test app made in C++ with the same Data. Unfortunately, the function return me -1 which means an error occurred, but the C++ app returns 23. So I guessed something is wrong with either my struct and/or the parameter I pass. Probably the int[]. I've tried to let the default marshal with ref, but didn't work. Any ideas?
EDIT
Since the type definition seem to be very important here is the def :
typedef void TVoid;
typedef bool TBool;
typedef char TCharA; // character 8
typedef TCharA TChar; // character 8
typedef wchar_t TCharW; // character 16
typedef signed char TInt08; // integer signed 8
typedef unsigned char TUnt08; // integer unsigned 8
typedef signed short TInt16; // integer signed 16
typedef unsigned short TUnt16; // integer unsigned 16
typedef signed long TInt32; // integer signed 32
typedef unsigned long TUnt32; // integer unsigned 32
typedef float TFlt32; // float 32
typedef double TFlt64; // float 64
The Size attribute you gave in the [StructLayout] attribute is a good hint. Verify that with this snippet:
int len = Marshal.SizeOf(typeof(Plate));
System.Diagnostics.Debug.Assert(len == 138);
The only way you are going to get passed this assertion is when you replace "bool" with "byte" (So TBool = 1 byte) and use a packing of 1:
[StructLayout(LayoutKind.Sequential, Pack=1), Serializable]
public struct Plate {
//...
}
If that still doesn't work then you'll really have to debug the unmanaged code.
What you really want is a way to debug this. The easiest way is to write your own dll that consumes this data type and see what happens to the struct on the other side.
I suspect that your real problem is structure alignment and how that's working out. What I see in your code is a bunch of elements with odd sizes (15, 28, 10). Chances are the target system has gone and aligned the structure elements on at least 2 byte, if not 4 byte boundaries. Nonethless you should check.
You can also save yourself some time by writing the C that consumes the actual struct and outputs the results of a bunch of offsetof() invocations on the struct elements.
You should be methodical in your approach instead of shotgun, and part of the methodology is to have measurements and feedback. This will give you both.
1) How many bytes is a TBool? 1? 4?
2) You can remove the StructLayout(LayoutKind.Sequential, Size = 138) attribute because it's Sequential by default and the Size can be determined by the runtime.
3) You can drop the [MarshalAs(UnmanagedType.LPArray)] attribute. The marshaller knows how to marshal arrays, but note, by default arrays are Marshalled as [IN], so if the c++ code is going to edit the contents of the arrays, you need to use the [IN,OUT] attribute.
How about the const TInt32* pTargets argument in the dll. I don't know how it's used, but that suggests a pointer to a single TInt32 instance, not an array of TInt32. Granted, it depends on how it's used in the code.