Scenario
This should be an easy task, but for some reason I can't get it going as intended. I have to marshal a basic C++ struct
during a reversed-P/Invoke call (unmanaged calling managed code).
The issue only arises when using bool
within the struct, so I just trim the C++ side down to:
struct Foo {
bool b;
};
Since .NET marshals booleans as 4-byte fields by default, I marshal the native boolean explicitly as a 1 byte-length field:
public struct Foo {
[MarshalAs(UnmanagedType.I1)] public bool b;
}
When I call an 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 printed. If I extend the structure with more fields, the alignment is correct and the data is not corrupt after marshalling.
Problem
For some reason, if I do not 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:
If I change the managed structure to hold a byte
instead of a 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 marshalled properly without an error to an unmanaged bool.
I don't unterstand two things here:
bool
as described above work, but as a return value give an error?byte
marshalled as UnmanagedType.I1
work for returns, but a bool
also marshalled with UnmanagedType.I1
does not?I hope my description makes sense -- if not, please let me know so I can change the wording.
EDIT: My current workaround is a managed struct like:
public struct Foo {
private byte b;
public bool B {
get { return b != 0; }
set { b = value ? (byte)1 : (byte)0; }
}
which honestly, I find quite ridiculous...
EDIT2: Here is an almost-MCVE. The managed assembly has been recompiled with proper symbol exports (using .export
and .vtentry
attributes in IL code), but there should be no difference to C++/CLI calls. So this code is not working "as-is" without doing the exports 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;
}
}
The underlying problem is the same as with this question - Why DllImport for C bool as UnmanagedType.I1 throws but as byte it works The exception you're getting is MarshalDirectiveException
- getting the remaining information about the exception is a bit trickier, but unnecessary.
In short, marshalling for return values only works for blittable structures. When you specify use a boolean field, the structure is no longer blittable (because bool
isn't blittable), and will no longer work for return values. This is simply a limitation of the marshaller, and it applies for both DllImport
and your attempts at "DllExport".
Quoting the relevant piece of documentation:
Structures that are returned from platform invoke calls must be blittable types. Platform invoke does not support non-blittable structures as return types.
It's not said outright, but the same thing applies when being invoked.
The simplest workaround is to stick with your "byte as a backing field, bool as a property" approach. Alternatively, you could use the C And of course, there's always the option of using a C++/CLI wrapper, or even just hiding 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 BOOL
instead, which will work just fine.Foo
type, and handle the proper conversion to the Foo++
type).
It's also possible to use a ref
argument instead of a return value. This is in fact a common pattern in unmanaged interop:
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 could also make your own boolean type, a simple struct
with a single byte
field, with implicit cast operators to and from bool
- it's not going to work exactly as a real bool
field, but it should work just fine most of the time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With