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.
Related
I am using VS 2015 Community with an ASP.NET MVC Web Application that uses a 3rd party C++ DLL I do not have source code for. Documentation is very scarce as is any helpful communication with the authors of the 3rd party DLL.
I've asked a related SO Question and received a good answer from #Steven. I've modified my code according to his answer and am trying to make a successful call to the 3rd party C++ DLL. The code:
// Call DLL
MyDLLInput _DLLInput = new MyDLLInput();
{
SomeList = new int[288],
...
SomeInt = 22,
SomeDbl = 1.45,
...
PathtoData = "C:\\Some\\Path\\To\\Data"
};
var ids = new int[] { 0, 12, 33, 67, 93 };
Array.Copy(ids, _DLLInput.SomeList, ids.Length);
// Call DLL Entry Point
MyDLLOutput _DLLOutput = MyDLL.Unit(_DLLInput);
Raises exception:
Method's type signature is not PInvoke compatible.
// C# Input STRUCT
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MyDLLInput
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 288)]
public int[] SomeList;
...
public int SomeInt;
public double SomeDbl;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string PathtoData;
};
// C# Output STRUCT
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MyDLLOutput
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 288)]
public int[] SomeList;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 288)]
public double[] SomeDblArray;
...
public int SomeInt; // Same as input
public double SomeDbl; // Same as input
}
// C# DLLImport
public class MyDLL
{
[DllImport("My_DLL.dll",
EntryPoint = "?Unit##YA?AUDLLOutput##UDLLInput###Z",
CallingConvention = CallingConvention.Cdecl)]
public static extern MyDLLOutput Unit(MyDLLInput UnitInput);
}
// C++ My_DLL.h
#define EPS_API __declspec(dllexport)
struct DLLInput
{
int SomeList[288];
int SomeInt;
double SomeDbl;
char PathtoData[256];
};
struct DLLOutput
{
int SomeList[288];
double SomeDblArray[288];
...
int SomeInt;
double SomeDbl;
};
EPS_API DLLOutput Unit(DLLInput UnitInput);
I think I must be close, but haven't been able to find any SO or Google results that help. Does anyone see what I'm doing wrong?
Expanding on what Ben has said.
You need to remove the const definitions to outside the interopable struct. By declaring a layout of the struct as sequential the compiler is expecting EXACTLY the same number of members, declared in EXACTLY the same order, with each type declared, EXACTLY the same size on both sides (i.e in C++ and C#). Those const declarations add to the memory layout and so the signatures are not compatible.
char is not the same size in C# and C++, byte is (although they can change depending on the platform, both types are guaranteed at least 8 bits). You could marshall a byte array instead of a string, but either is fine.
To Create a byte array which is compatible with C++ char array (same size).
UTF8Encoding utf8 = new UTF8Encoding();
public byte[] PathToData = new byte[256];
PathToData = utf8.GetBytes(pathToDataString);
On another note though, if documentation is scarce and the authors are not helpful, personally I would think twice about using this 3rd party. Calling it is one thing, debugging it is another.
I am converting c/c++ structures into C# standards.
C/C++ Structures:
typedef struct _sta_conn_info{
STA_CONNECT_STATE connect_state;//Enum
STA_ASSOC_STATE assoc_state;//Enum
unsigned char bssid[6];
unsigned char ssid[34];
unsigned long channel;
enum mode mode;//Enum
unsigned long signalStrength;
unsigned long noiseLevel;
STA_AUTH_ALG auth_alg;//enum
STA_ENCRYPT_ALG encrypt_alg;//enum
}STA_CONN_INFO;
typedef struct _NDISUIO_QUERY_OID
{
NDIS_OID Oid;
PTCHAR ptcDeviceName;
UCHAR Data[sizeof(ULONG)];
} NDISUIO_QUERY_OID, *PNDISUIO_QUERY_OID;
Respective C# structures:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _sta_conn_info
{
public _sta_connect_state connect_state;
public _sta_assoc_state assoc_state;
[MarshalAs(UnmanagedType.ByValArray,SizeConst = 6)]
public char[] bssid ;//= new char[6];
[MarshalAs(UnmanagedType.ByValArray,SizeConst = 34)]
public char[] ssid ;//= new char[34]
public uint channel;
public mode mode;
public uint signalStrength;
public uint noiseLevel;
public _sta_auth_alg auth_alg;
public _sta_encrypt_alg encrypt_alg;
}
QUERY STRUCT:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _NDISUIO_QUERY_OID
{
public uint Oid;
[MarshalAs(UnmanagedType.LPWStr)]
public string ptcDeviceName;
public byte[] Data;
};
I converted the data types using this >>>reference
Marshal.SizeOf() is working in WIN CE. I tested it.
If my structure conversion is fine then definitely Marshal.SizeOf() will work to get the size of the structure, but it is throwing exceptions and returning error code 87 in DeviceIoControl() API.
Can anyone clarify me about the conversions and let me know If I did anything wrong.
For bssid and ssid the C++ declarations are:
unsigned char bssid[6];
unsigned char ssid[34];
Now, unsigned char is a single byte and is typically used for byte arrays rather than text. So the C# should be:
[MarshalAs(UnmanagedType.ByValArray,SizeConst = 6)]
public byte[] bssid ;//= new byte[6];
[MarshalAs(UnmanagedType.ByValArray,SizeConst = 34)]
public byte[] ssid ;//= new byte[34]
Your use of char in the C# is not correct because char is two bytes wide in C#.
In _NDISUIO_QUERY_OID where you have
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = sizeof(uint))]
public byte[] Data;
I believe that you need to use ByValArray rather than ByValTStr. But as we have discussed in many of your recent questions, the exact meaning of this member is unclear. Is it really a fixed length byte array, or is it a variable length buffer? Do you have sample C++ code that works? That would settle the debate once and for all.
OK, from the header nuiouser.h header file I have this:
//
// Structure to go with IOCTL_NDISUIO_QUERY_OID_VALUE.
// The Data part is of variable length, determined by
// the input buffer length passed to DeviceIoControl.
//
typedef struct _NDISUIO_QUERY_OID
{
NDIS_OID Oid;
#ifdef UNDER_CE
//
// In CE land app is allowed to query without having to do
// IOCTL_NDISUIO_OPEN_DEVICE
// Hence the device name to query argument needed..
// For app that does IOCTL_NDISUIO_OPEN_DEVICE this argument
// is then not necessary..
//
PTCHAR ptcDeviceName;
#endif
UCHAR Data[sizeof(ULONG)];
} NDISUIO_QUERY_OID, *PNDISUIO_QUERY_OID;
Which tells you conclusively that Data is variable length. You'll need to allocate the struct with AllocHGlobal and do all the marshalling by hand I am afraid.
I am having a problem with marshalling a C character array. I have the following C# structure:
[StructLayout(LayoutKind.Explicit, Size = 16, CharSet = CharSet.Ansi), Serializable]
internal struct Header
{
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 4)]
[FieldOffset(0)]
public string header;
[FieldOffset(4)]
public int version;
[FieldOffset(8)]
public int diroffset;
[FieldOffset(12)]
public int direntries;
}
and the following code to read this structure from a stream:
public static T ReadStruct<T>(this Stream stream) where T : struct
{
var sz = Marshal.SizeOf(typeof(T));
var buffer = new byte[sz];
stream.Read(buffer, 0, sz);
var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var structure = (T) Marshal.PtrToStructure(
pinnedBuffer.AddrOfPinnedObject(), typeof(T));
pinnedBuffer.Free();
return structure;
}
Now my problem is that the header field misses a character after the struct is read. The file where the struct is read from contains the four bytes VPVP but after the struct has been read by ReadStruct the header string only contains VPV. If I take a look at the byte array in the read function in the debugger then that array contains the values 86, 80, 86, 80 which is VPVP. I also tried using LayoutKind.Sequential for the StructLayout but that didn't change anything.
Am I doing something wrong or why is there a character missing in my string?
The problem you're having lies in the struct definition, not in writing the bytes to it.
The problem lies right here:
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 4)]
As you've stated, you're writing out the text VPVP, which is 4 characters long, you'd think. This, however, is not the case. In C, you could declare the string as such:
char mystring[] = { 'V', 'P', 'V', 'P', '\0' };
You need that null character (\0) at the end, to mark off the end of the string. You need to take this into account when marshalling, because you need to reserve space for that "null terminator byte", if you do not, the C# string will add it for you in the available memory, so it will eat away your last character. So if you're gonna use a null-terminated string, you will have to make it of length 5.
EDIT: Here is a better solution, where you don't have to worry about null-terminators, you just use a char[] (and you also keep the magic 16 byte size):
[StructLayout(LayoutKind.Explicit, Size = 16, CharSet = CharSet.Ansi), Serializable]
internal struct Header
{
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 4)]
[FieldOffset(0)]
private char[] headerCharArray;
public string header
{
get { return new string(headerCharArray); }
set
{
if (value.Length == 4)
{
headerCharArray = value.ToArray();
}
else
{
throw new InvalidOperationException("String length was not 4.");
}
}
}
[FieldOffset(4)]
public int version;
[FieldOffset(8)]
public int diroffset;
[FieldOffset(12)]
public int direntries;
}
That way the char[] is stored in memory, and you can access it as a string through the property, which doesn't take in any memory of the struct itself.
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.
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.