Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why DllImport for C bool as UnmanagedType.I1 throws but as byte it works

Here is a simple C code (VS 2013 C++ project, "compiled as C"):

typedef struct {
    bool bool_value;
} BoolContainer;

BoolContainer create_bool_container (bool bool_value)
{
    return (BoolContainer) { bool_value };
}

Here is my P/Invoke wrapper

public partial class NativeMethods
{
    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
    public static extern BoolContainer create_bool_container([MarshalAs(UnmanagedType.I1)] bool bool_value);
}

Here are the managed versions of the BoolContainer:

First is throwing MarshalDirectiveException: Method's type signature is not PInvoke compatible.:

public struct BoolContainer // Marshal.SizeOf(typeof(BoolContainer)) = 1
{
    [MarshalAs(UnmanagedType.I1)] // same with UnmanagedType.U1
    public bool bool_value;
}

Second is working:

public struct BoolContainer // Marshal.SizeOf(typeof(BoolContainer)) = 1
{
    public byte bool_value;
}

Test code:

BoolContainer boolContainer = NativeMethods.create_bool_container(true);

Is seems like DllImport ignores MarshalAs for any boolean members in the returned structure. Is there a way to keep bool in the managed declaration?

like image 803
rgripper Avatar asked Mar 22 '23 14:03

rgripper


1 Answers

Structs as return types are pretty difficult in interop, they don't fit a CPU register. This is handled in native code by the caller reserving space on its stack frame for the return value and passing a pointer to the callee. Which then copies the return value through the pointer. In general a pretty troublesome feature, different C compilers have different strategies to pass that pointer. The pinvoke marshaller assumes MSVC behavior.

This is a problem with structs that are not blittable, in other words when its layout is no longer an exact match with the unmanaged struct that the native code needs. That requires an extra step to convert the unmanaged struct to the managed struct, the equivalent of Marshal.PtrToStructure(). You see this for example if you add a string field to your struct. That makes it non-blittable since the C string has to be converted to a System.String, you'll get the exact same exception.

A similar kind of problem in your case, bool is also a very difficult type to interop with. Caused by the C language not having the type and it getting added later with highly incompatible choices. It is 1 byte in C++ and the CLR, 2 bytes in COM, 4 bytes in C and the winapi. The pinvoke marshaller picked the winapi version as the default, making it match BOOL, the most common reason to pinvoke. This forced you to add the [MarshalAs] attribute and that also requires extra work, again to be done by the equivalent of Marshal.PtrToStructure().

So what the pinvoke marshaller is missing is the support for this extra step, custom marshaling of the return value after the call. Not sure if this was a design constraint or a resource constraint. Probably both, ownership of non-blittable struct members is completely unspecified in unmanaged languages and guessing at it, especially as a return value, very rarely turns out well. So they may well have made the call that working on the feature just wasn't worth the low odds of success. This is a guess of course.

Using byte instead is a fine workaround.

like image 138
Hans Passant Avatar answered Apr 06 '23 11:04

Hans Passant