C#编组布尔

Pue*_*tis 8 c# pinvoke interop marshalling

脚本

这应该是一项简单的任务,但由于某种原因,我无法按预期进行.我必须struct在反向P/Invoke调用(非托管调用托管代码)期间编组基本C++ .

只有bool在struct中使用时才出现这个问题,所以我只是将C++方面修改为:

struct Foo {
    bool b;
};
Run Code Online (Sandbox Code Playgroud)

由于.NET默认情况下将布尔值编组为4字节字段,因此我将本机布尔值显式编组为1字节长度字段:

public struct Foo {
    [MarshalAs(UnmanagedType.I1)] public bool b;
}
Run Code Online (Sandbox Code Playgroud)

当我使用以下签名和正文调用导出的托管静态方法时:

public static void Bar(Foo foo) {
    Console.WriteLine("{0}", foo.b);
}
Run Code Online (Sandbox Code Playgroud)

我打印出正确的布尔alpha表示.如果我使用更多字段扩展结构,则对齐是正确的,并且在编组后数据不会损坏.

问题

出于某种原因,如果我不将此编组struct作为参数传递,而是通过值传递返回类型:

public static Foo Bar() {
    var foo = new Foo { b = true };
    return foo;
}
Run Code Online (Sandbox Code Playgroud)

应用程序崩溃时出现以下错误消息:

在此输入图像描述

如果我更改托管结构以保持byte而不是abool

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

public static Foo Bar() {
    var foo = new Foo { b = 1 };
    return foo;
}
Run Code Online (Sandbox Code Playgroud)

返回值正确编组而没有错误到非托管bool.

我不会在这里说两件事:

  1. 为什么bool如上所述编组的参数有效,但作为返回值会产生错误?
  2. 为什么byte作为UnmanagedType.I1回报的工作bool被编组,但是也被编组为UnmanagedType.I1没有?

我希望我的描述有意义 - 如果没有,请告诉我,以便我可以改变措辞.

编辑: 我目前的解决方法是托管结构,如:

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

老实说,我觉得很荒谬......

编辑2:这是一个几乎是MCVE.已使用正确的符号导出(在IL代码中使用.export.vtentry属性)重新编译托管程序集,但C++/CLI调用应该没有区别.因此,如果不手动执行导出,此代码将无法"按原样"运行:

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;
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Lua*_*aan 6

潜在的问题与这个问题是一样的 - 为什么DllImport for C bool as UnmanagedType.I1抛出但是作为字节它起作用你得到的异常是MarshalDirectiveException- 得到关于异常的剩余信息有点棘手,但是没有必要.

简而言之,对返回值进行编组仅适用于blittable结构.当您指定使用布尔字段时,结构不再是blittable(因为bool不是blittable),并且将不再适用于返回值.这只是编组人员的限制,它适用DllImport于"DllExport"和你的尝试.

引用相关文档:

从平台调用调用返回的结构必须是blittable类型.平台调用不支持非blittable结构作为返回类型.

它不是直接说的,但在被调用时同样适用.

最简单的解决方法是坚持使用"byte作为支持字段,bool作为属性"方法.或者,您可以使用C BOOL代替,这将工作得很好.当然,总是可以选择使用C++/CLI包装器,或者甚至只是在您的帮助器方法中隐藏结构的真实布局(在这种情况下,您的导出方法将调用另一种处理实际Foo类型的方法,并且处理正确的转换Foo++类型).

也可以使用ref参数而不是返回值.这实际上是非托管互操作中的常见模式:

typedef void(__stdcall *Pt2GetFoo)(Foo* foo);

Foo f2 = Foo();
getFoo(&f2);
Run Code Online (Sandbox Code Playgroud)

在C++方面,和

public static void GetFoo(ref Foo foo)
{
    foo = new Foo { b = true };
}
Run Code Online (Sandbox Code Playgroud)

在C#方面.

你也可以创建自己的布尔类型,一个struct带有单个byte字段的简单类型,带有隐式的转换运算符bool- 它不会像真实bool字段一样工作,但它应该在大多数情况下工作得很好.

  • 嗯,您没有正确阅读该副本。使用 BOOL 不是一种解决方法,它不是 blittable。不可能,.NET bool 是 1 个字节,而 BOOL 是 4 个字节。 (2认同)