C # Marshalling bool

Scenario

This should be an easy task, but for some reason I cannot achieve its goal. I have to create a basic C ++ struct during the P / Invoke callback (managed code of an unmanaged call).

The problem only occurs when using bool inside the structure, so I just trim the C ++ side to:

 struct Foo { bool b; }; 

Since .NET marshals have 4 byte fields by default, by default I marshaled a boolean boolean value as a 1 byte field:

 public struct Foo { [MarshalAs(UnmanagedType.I1)] public bool b; } 

When I call the exported managed static method with the following signature and body:

 public static void Bar(Foo foo) { Console.WriteLine("{0}", foo.b); } 

I get the correct boolean alpha representation. If I expand the structure with a large number of fields, the alignment will be correct and the data will not be damaged after sorting.

Problem

For some reason, if I don't pass this marshalled struct as an argument, but rather as a return type by value:

 public static Foo Bar() { var foo = new Foo { b = true }; return foo; } 

The application crashes with the following error message:

enter image description here

If I change the managed structure by holding byte instead of bool

 public struct Foo { [MarshalAs(UnmanagedType.I1)] public byte b; } public static Foo Bar() { var foo = new Foo { b = 1 }; return foo; } 

the return value is sorted correctly without an unmanaged bool error.

I do not understand two things here:

  • Why does the parameter mapped to bool as described above work, but how does the return value give an error?
  • Why is UnmanagedType.I1 handled as UnmanagedType.I1 to return, but the bool also associated with UnmanagedType.I1 does not work?

I hope my description makes sense - if not, let me know so that I can change the wording.

EDIT: My current workaround is a managed framework, for example:

 public struct Foo { private byte b; public bool B { get { return b != 0; } set { b = value ? (byte)1 : (byte)0; } } 

which honestly, I find it pretty funny ...

EDIT2: That's almost MCVE. The managed assembly was recompiled with proper export of characters (using the .export and .vtentry in the IL code), but there should be no difference with C ++ / CLI calls. Thus, this code does not work β€œas is” without doing the export manually:

C ++ (native.dll):

 #include <Windows.h> struct Foo { bool b; }; typedef void (__stdcall *Pt2PassFoo)(Foo foo); typedef Foo (__stdcall *Pt2GetFoo)(void); int main(int argc, char** argv) { HMODULE mod = LoadLibraryA("managed.dll"); Pt2PassFoo passFoo = (Pt2PassFoo)GetProcAddress(mod, "PassFoo"); Pt2GetFoo getFoo = (Pt2GetFoo)GetProcAddress(mod, "GetFoo"); // Try to pass foo (THIS WORKS) Foo f1; f1.b = true; passFoo(f1); // Try to get foo (THIS FAILS WITH ERROR ABOVE) // Note that the managed method is indeed called; the error // occurs upon return. If 'b' is not a 'bool' but an 'int' // it also works, so there must be something wrong with it // being 'bool'. Foo f2 = getFoo(); return 0; } 

C # (managed.dll):

 using System; using System.Runtime.InteropServices; public struct Foo { [MarshalAs(UnmanagedType.I1)] public bool b; // When changing the above line to this, everything works fine! // public byte b; } /* .vtfixup [1] int32 fromunmanaged at VT_01 .vtfixup [1] int32 fromunmanaged at VT_02 .data VT_01 = int32(0) .data VT_02 = int32(0) */ public static class ExportedFunctions { public static void PassFoo(Foo foo) { /* .vtentry 1:1 .export [1] as PassFoo */ // This prints the correct value, and the // method returns without error. Console.WriteLine(foo.b); } public static Foo GetFoo() { /* .vtentry 2:1 .export [2] as GetFoo */ // The application crashes with the shown error // message upon return. var foo = new Foo { b = true; } return foo; } } 
+6
source share
1 answer

The main problem is the same as with this question - Why DllImport for C bool as UnmanagedType.I1 throws but works as an byte The exception you get is a MarshalDirectiveException - getting the remaining exception information is a bit more complicated, but not necessary.

In short, sorting for return values ​​only works for blittable structures. When you specify the use of a boolean field, the structure ceases to be blittable (because bool not blittable) and will no longer work for returned values. This is simply a limitation of the marshaller, and it applies to both DllImport and your "DllExport" attempts.

Quoting the relevant documentation:

Structures that return from platform invocation calls must be blittable types. The platform call does not support non-blittable structures as return types.

This is not explicitly stated, but the same applies when invoked.

The simplest workaround is to use your byte as the basis for support, bool as properties. Alternatively, you can use C bool instead, which will work just fine. And, of course, there is always the opportunity to use the C ++ / CLI shell or even just hide the real layout of the structure in your helper methods (in this case, your export methods will call another method that deals with the real Foo type, and handle the proper conversion to type Foo++ ).

It is also possible to use the ref argument instead of the return value. This is actually a generic pattern in an unmanaged interaction:

 typedef void(__stdcall *Pt2GetFoo)(Foo* foo); Foo f2 = Foo(); getFoo(&f2); 

on the C ++ side and

 public static void GetFoo(ref Foo foo) { foo = new Foo { b = true }; } 

on the c # side.

You can also create your own boolean type, a simple struct with one byte field with implicit cast operations to and from bool - it will not work exactly like a real bool field but it should work very well most of the time.

+5
source

All Articles