C# 编码 bool

标签 c# interop pinvoke marshalling

场景

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

只有在结构中使用 bool 时才会出现此问题,因此我只是将 C++ 方面缩减为:

struct Foo {
    bool b;
};

由于 .NET 默认将 bool 值编码为 4 字节字段,因此我将 native bool 值显式编码为 1 字节长度的字段:

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

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

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

我打印了正确的 bool alpha 表示。如果我用更多字段扩展结构,则对齐是正确的,数据在编码后不会损坏。

问题

出于某种原因,如果我不将这个编码的 struct 作为参数传递,而是作为按值返回类型传递:

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

应用程序崩溃并显示以下错误消息:

enter image description here

如果我更改托管结构以保存 byte 而不是 bool

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

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

返回值被正确编码,没有错误到非托管 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; }
}

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

EDIT2:这是一个几乎 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;
}

C#(托管.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;
    }
}

最佳答案

根本问题与这个问题相同 - Why DllImport for C bool as UnmanagedType.I1 throws but as byte it works您得到的异常是 MarshalDirectiveException - 获取有关异常的剩余信息有点棘手,但没有必要。

简而言之,返回值的编码仅适用于 blittable 结构。当您指定使用 bool 字段时,该结构不再是 blittable(因为 bool 不是 blittable),并且将不再适用于返回值。这只是编码器的一个限制,它适用于 DllImport。以及您对“DllExport”的尝试。

引用相关文档:

Structures that are returned from platform invoke calls must be blittable types. Platform invoke does not support non-blittable structures as return types.

虽然不是直截了当的说,但是调用的时候也是一样的。

最简单的解决方法是坚持使用“字节作为支持字段, bool 作为属性”的方法。 或者,您可以使用 C BOOL相反,它会工作得很好。 当然,始终可以选择使用 C++/CLI 包装器,或者甚至只是在您的辅助方法中隐藏结构的真实布局(在这种情况下,您的导出方法将调用另一个处理真实 Foo 类型的方法,并处理到 Foo++ 类型的正确转换。

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

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

Foo f2 = Foo();
getFoo(&f2);

在 C++ 方面,和

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

在 C# 方面。

你也可以制作你自己的 bool 类型,一个简单的 struct用一个byte字段,隐式转换运算符往返于 bool - 它不会像真正的一样工作 bool领域,但它应该在大多数时候都能正常工作。

关于C# 编码 bool ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32110152/

相关文章:

c# - 查找具有常量的所有单元格会使 Excel 崩溃

c# - 在通过 Wix 管理的自定义操作安装之前优雅地关闭应用程序

c# - 将 C 结构编码到 C#

c# - 模拟服务提供者 GetServices

c# - 返回多个枚举值的最佳方法是什么? (Java 和 C#)

c# - MSTest v2 有序测试

c# - PInvoke 一个字节数组的数组

javascript - 带有动态 Javascript 站点的 webbrowser documentcompleted 事件

C# 以编程方式访问 Excel 宏

java - 未知故障(cmd :Failure calling service package: Broken pipe (32))