【问题标题】:C# Marshalling boolC#编组布尔
【发布时间】:2015-08-20 05:27:18
【问题描述】:

场景

这应该是一件容易的事,但由于某种原因,我无法按预期进行。我必须在反向 P/Invoke 调用(非托管调用托管代码)期间编组一个基本的 C++ struct

只有在结构中使用bool 时才会出现此问题,因此我将 C++ 端向下修剪为:

struct Foo {
    bool b;
};

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

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

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

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

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

问题

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

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

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

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

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

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

返回值被正确编组到非托管布尔值,没有错误。

这里有两点我不明白:

  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# (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;
    }
}

【问题讨论】:

  • 我们可以有一个 MCVE 吗?所以我们也可以看到函数调用。
  • @DavidHeffernan 我尽我所能添加了一个 MCVE。由于我使用反向 P/Invoke 机制并直接操作 IL 代码,因此如果不手动更改程序集,它不会开箱即用。
  • 操纵 IL?唔。这肯定是相关的!
  • @DavidHeffernan 这里描述了基本概念(codeproject.com/Articles/37675/…)。您可以在 inernet 上的几篇文章中找到这个机制,但在这里简单地解释太多了。但是这个应该(希望)不是这里的问题,因为它直接反映了 C++/CLI 行为。这是 IMO 问题的唯一机会是 ilasm.exe 生成错误的编组 thunk - 这将是报告给 Microsoft Connect 进行修复的候选对象。
  • 你能避免使用 C++ 进行互操作吗?在互操作结构和函数指针中坚持使用纯 C 语言,我得到了更好的结果。

标签: c# interop pinvoke marshalling


【解决方案1】:

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

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

引用相关文档:

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

没有直接说出来,但在被调用时也是如此。

最简单的解决方法是坚持使用“字节作为支持字段,布尔作为属性”的方法。 或者,您可以改用 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# 方面。

您还可以创建自己的布尔类型,一个简单的 struct 和一个 byte 字段,以及与 bool 之间的隐式转换运算符 - 它不会像真正的 bool 字段那样工作,但它应该在大多数情况下都可以正常工作。

【讨论】:

  • 嗯,您没有正确阅读该副本。使用 BOOL 不是一种解决方法,它不是 blittable。不可能,.NET bool 是 1 个字节,BOOL 是 4 个字节。
  • @HansPassant 哦,我的错。固定。
猜你喜欢
  • 2017-08-18
  • 1970-01-01
  • 2010-10-12
  • 1970-01-01
  • 2016-04-13
  • 2023-03-17
  • 1970-01-01
  • 2014-12-09
  • 2016-12-27
相关资源
最近更新 更多