views:

243

answers:

4

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
A: 

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.

hjb417
1) 12) OK3) UnmanagedType.LPArray was added yesterday to fix an exception the CLR was throwing on structs. Maybe uneccesary on the int[] but removing it didnMt solve the problem.
lucian.jp
1) What was the CLR Exception that was thrown?2) Change the type of Light and Roll from bool to byte.
hjb417
1) http://stackoverflow.com/questions/1833368/p-invoke-throw-system-executionengineexception2) Done, but didn't fix
lucian.jp
I tried to put the two struct [Out], the int[] came back as is, but the Plate [] came back with empty objects while all the objects where filled correctly before the call. This points me that there is a bug with the Plate struct.
lucian.jp
1) On the DllImport, are you setting the DllImport.CallingConvention to StdCall? 2) What's a TCharA? 2 bytes? 1 bytes? 3) What encoding does this library expect stuff to be in? ascii? unicode? 4) for debugging purposes, change the types of the string fields to fixed char[] fields or fixed byte[] fields depending on what TChar is. ( http://msdn.microsoft.com/en-us/library/zycewsya.aspx )
hjb417
E.x.: [DllImport("tyrfde.dll", EntryPoint = "tyrfdeSetResults", CallingConvention=CallingConvention.StdCall)]Also, you may want to look at 'Specifying a Character Set' for the DllImportAttribute.CharSet field @ http://msdn.microsoft.com/en-us/library/7b93s42f.aspx
hjb417
A: 

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.

scottm
it's a l and not a 1. 28 long.
hjb417
hjb417 is right
lucian.jp
+1  A: 

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.

plinth
+1  A: 

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.

Hans Passant
The comparison gave me 144 instead of 138, so the Bool was marshalled as 4 instead of 1 as the first answer was suggesting. Pack =1 is also required, else it crash this was the definitive major point since I already had tested the byte instead of bool. Since Pack=1 was my major issue, I flag this as the answer.
lucian.jp