Marshaling byval C-structure as return value in C#

2019-05-07 18:22发布

问题:

I have unmanaged code:

...
typedef struct foo  
{  
 int  a;  
 bool b
 int  c;  
} FOO,*LPFOO;
....
__declspec(dllexport) FOO __stdcall GetFoo()  
{  
   FOO f;  
   <some work>  
   return f;   
}  
....

I've declare C# prototype for GetFoo function:

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    private struct Foo
    {
      public int  a;  
      public bool b
      public int  c; 
    };

    [DllImport("foo.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
    [return:MarshalAs( UnmanagedType.Struct)]        
    private static extern Foo GetFoo();

But when I calling GetFoo from C# code I alway have MarshalDirectiveException- Method's type signature is not PInvoke compatible. How I should declare C# prototype?

回答1:

Yes, functions that return a structure tend to be difficult to interop with. Such a structure has to be blittable so the pinvoke marshaller can pass a pointer to the function, ready for it to write the return value. Being "blittable" means that the structure layout in managed code needs to be identical to the unmanaged layout of the structure. If it is not then a copy needs to be made, the pinvoke marshaller does not want to make that copy in the specific case of a return value.

The bool type is an interop problem, different runtimes made different choices. It tends to be 4 bytes in C (compare to the Windows BOOL type, also the default for pinvoke), 2 bytes in COM interop (aka VARIANT_BOOL), 1 byte in C++, 1 byte in the CLR. Since the target runtime is unknown, the CLR cannot guess which choice is right. BOOL is the default, 4 bytes.

Even using [MarshalAs(UnmanagedType.U1)] to force an exact match does not make it blittable. Which is pretty odd, I consider this a CLR bug. A good workaround is to replace it with byte, you can use a property to wrap it back to a bool. Beware that there were a lot of mistakes in the posted snippet, I made this version work:

using System;
using System.Runtime.InteropServices;

class Program {
    static void Main(string[] args) {
        Foo value = GetFoo();
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct Foo {
        public int a;
        private byte _b;
        public bool b {
            get { return _b != 0; }
        }
        public int c;
    };

    [DllImport(@"c:\projects\consoleapplication3\debug\cpptemp10.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "_GetFoo@0")]
    private static extern Foo GetFoo(/*int CoreIndex*/);
}

typedef struct foo  
{  
    int  a;  
    bool b;
    int  c;  
} FOO,*LPFOO;

extern "C" __declspec(dllexport) 
FOO __stdcall GetFoo()  
{  
    FOO f;  
    f.a = 42;
    f.b = true;
    f.c = 101;
    return f;   
}