2
votes

I have some problems working with boolean types and marshalling this in a struct back and forth between C# and C. I am very rusty in C but hopefully there's nothing crucially wrong in that part.

As far as I've read/seen, .NET Boolean and C# bool type is 4 bytes long whilst the C type bool is only 1 byte. For memory footprint reasons, I do not which to use the defined BOOL 4 bytes version in the C code.

Here is some simple test code that hopefully will make my questions clear:


C code:

typedef struct
{
        double SomeDouble1;
        double SomeDouble2;
        int SomeInteger;
        bool SomeBool1;
        bool SomeBool2;
} TestStruct;

extern "C" __declspec(dllexport) TestStruct* __stdcall TestGetBackStruct(TestStruct* structs);

__declspec(dllexport) TestStruct* __stdcall TestGetBackStruct(TestStruct* structs)
{
    return structs;
}

I call this code in C# using the following definitions:

    [StructLayout(LayoutKind.Explicit)]
    public struct TestStruct
    {
        [FieldOffset(0)]
        public double SomeDouble1;
        [FieldOffset(8)]
        public double SomeDouble2;

        [FieldOffset(16)]
        public int SomeInteger;

        [FieldOffset(17), MarshalAs(UnmanagedType.I1)]
        public bool SomeBool1;
        [FieldOffset(18), MarshalAs(UnmanagedType.I1)]
        public bool SomeBool2;
    };

    [DllImport("Front.dll", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr TestGetBackStruct([MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] TestStruct[] structs);

and here is the actual test function in C#:

    [Test]
    public void Test_CheckStructParsing()
    {
        var theStruct = new TestStruct();
        theStruct.SomeDouble1 = 1.1;
        theStruct.SomeDouble2 = 1.2;
        theStruct.SomeInteger = 1;
        theStruct.SomeBool1 = true;
        theStruct.SomeBool2 = false;

        var structs = new TestStruct[] { theStruct };

        IntPtr ptr = TestGetBackStruct(structs);

        var resultStruct = (TestStruct)Marshal.PtrToStructure(ptr, typeof(TestStruct));
    }

This works in the sense that I do get a struct back (using the debugger to inspect it), but with totally wrong values. I.e. the marshalling does not work at all. I've tried different version of the C# struct without success. So here are my questions (1 & 2 most important):

  1. Is the C function correct for this purpose?
  2. How is the struct to be written correctly in order to get me the correct values in the struct back to C#? (Is it even necessary to define the struct with the StructLayout(LayoutKind.Explicit) attribute using the FieldOffset values or can I use StructLayout(LayoutKind.Sequential) instead)?
  3. Since I am returning a pointer to the TestStruct in C, I guess it should be possible to get back an array of TestStructs in C#. But this does not seem to be possible using the Marshal.PtrToStructure function. Would it be possible in some other way?
  4. Apparantly it is possible to use something called unions in C by having multiple struct fields point to the same memory allocation using the same FieldOffset attribute value. I understand this, but I still don't get yet when such scenario would be useful. Please enlighten me.
  5. Can someone recommend a good book on C# P/Invoke to C/C++? I am getting a bit tired of getting pieces of information here and there on the web.

Much obliged for help with these questions. I hope they were not too many.

1

1 Answers

6
votes

Stop using LayoutKind.Explicit and get rid of the FieldOffset attributes and your code will work. Your offsets were not correctly aligning the fields.

public struct TestStruct
{
    public double SomeDouble1;
    public double SomeDouble2;
    public int SomeInteger;
    [MarshalAs(UnmanagedType.I1)]
    public bool SomeBool1;
    [MarshalAs(UnmanagedType.I1)]
    public bool SomeBool2;
};

Declare the function in C# like this:

public static extern void TestGetBackStruct(TestStruct[] structs);

The default marshalling will match your C++ declaration (your code is C++ rather than C in fact) but you must make sure that your allocate the TestStruct[] parameter in the C# code before calling the function. Normally you would also pass the length of the array as a parameter so that the C++ code knows how many structs there are.

Please don't try to return the array of structures from the function. Use the structs parameter as an in/out parameter.

I know of no book with an emphasis on P/Invoke. It appears to be something of a black art!