C# 编组布尔
C# Marshalling bool
场景
这应该是一项简单的任务,但由于某种原因我无法按预期进行。我必须在 reversed-P/Invoke 调用(非托管调用托管代码)期间编组基本 C++ struct
。
只有在结构中使用 bool
时才会出现此问题,所以我只是 trim 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
作为参数传递,而是作为 return 按值类型传递:
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;
}
return 值被正确编组到非托管布尔值,没有错误。
这里有两件事我不明白:
- 为什么如上所述用
bool
编组的参数有效,但作为 return 值却给出错误?
- 为什么
byte
编组为 UnmanagedType.I1
可以工作 return 秒,而 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 调用没有区别.因此,如果不手动执行导出,此代码将无法工作 "as-is":
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;
}
}
根本问题与这个问题相同 - Why DllImport for C bool as UnmanagedType.I1 throws but as byte it works 你得到的异常是 MarshalDirectiveException
- 获取有关异常的剩余信息有点棘手,但没有必要。
简而言之,return 值的编组仅适用于 blittable 结构。当您指定使用布尔字段时,该结构不再是 blittable(因为 bool
不可 blittable),并且将不再适用于 return 值。这只是编组器的一个限制,它适用于 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.
没有直接说,调用的时候也是一样。
最简单的解决方法是坚持使用您的 "byte as a backing field, bool as a property" 方法。 或者,您可以改用 C BOOL
,它会工作得很好。当然,始终可以选择使用 C++/CLI 包装器,甚至只是隐藏您的辅助方法中结构的真实布局(在这种情况下,您的导出方法将调用另一个处理真实 Foo
类型的方法,并处理到 Foo++
类型的正确转换)。
也可以使用 ref
参数代替 return 值。这实际上是非托管互操作中的常见模式:
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
字段,但它在大多数时间应该都可以正常工作。
场景
这应该是一项简单的任务,但由于某种原因我无法按预期进行。我必须在 reversed-P/Invoke 调用(非托管调用托管代码)期间编组基本 C++ struct
。
只有在结构中使用 bool
时才会出现此问题,所以我只是 trim 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
作为参数传递,而是作为 return 按值类型传递:
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;
}
return 值被正确编组到非托管布尔值,没有错误。
这里有两件事我不明白:
- 为什么如上所述用
bool
编组的参数有效,但作为 return 值却给出错误? - 为什么
byte
编组为UnmanagedType.I1
可以工作 return 秒,而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 调用没有区别.因此,如果不手动执行导出,此代码将无法工作 "as-is":
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;
}
}
根本问题与这个问题相同 - Why DllImport for C bool as UnmanagedType.I1 throws but as byte it works 你得到的异常是 MarshalDirectiveException
- 获取有关异常的剩余信息有点棘手,但没有必要。
简而言之,return 值的编组仅适用于 blittable 结构。当您指定使用布尔字段时,该结构不再是 blittable(因为 bool
不可 blittable),并且将不再适用于 return 值。这只是编组器的一个限制,它适用于 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.
没有直接说,调用的时候也是一样。
最简单的解决方法是坚持使用您的 "byte as a backing field, bool as a property" 方法。 或者,您可以改用 C 当然,始终可以选择使用 C++/CLI 包装器,甚至只是隐藏您的辅助方法中结构的真实布局(在这种情况下,您的导出方法将调用另一个处理真实 BOOL
,它会工作得很好。Foo
类型的方法,并处理到 Foo++
类型的正确转换)。
也可以使用 ref
参数代替 return 值。这实际上是非托管互操作中的常见模式:
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
字段,但它在大多数时间应该都可以正常工作。