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:
- Why does a paramter marshalled with
bool
as described above work, but as a return value give an error? - Why does a
byte
marshalled asUnmanagedType.I1
work for returns, but abool
also marshalled withUnmanagedType.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;
}
}
ilasm.exe
generates faulty marshalling thunks - which would be a candidate to be reported to Microsoft Connect for fixing. – LazaroMarshalDirectiveException
tries to tell us). – NummuliteDllImport
and the export. – Nummulitebyte
instead ofbool
as also suggested in the answer of the duplicate. – Lazaromscorwks.dll
. I'm not sure if all have been fixed since (the article is about .NET 1.0 and 1.1), they certainly tried. – Nummulite