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.
我不会在这里说两件事:
bool如上所述编组的参数有效,但作为返回值会产生错误?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)
潜在的问题与这个问题是一样的 - 为什么DllImport for C bool as UnmanagedType.I1抛出但是作为字节它起作用你得到的异常是MarshalDirectiveException- 得到关于异常的剩余信息有点棘手,但是没有必要.
简而言之,对返回值进行编组仅适用于blittable结构.当您指定使用布尔字段时,结构不再是blittable(因为bool不是blittable),并且将不再适用于返回值.这只是编组人员的限制,它适用DllImport于"DllExport"和你的尝试.
引用相关文档:
从平台调用调用返回的结构必须是blittable类型.平台调用不支持非blittable结构作为返回类型.
它不是直接说的,但在被调用时同样适用.
最简单的解决方法是坚持使用"byte作为支持字段,bool作为属性"方法.或者,您可以使用C 当然,总是可以选择使用C++/CLI包装器,或者甚至只是在您的帮助器方法中隐藏结构的真实布局(在这种情况下,您的导出方法将调用另一种处理实际BOOL代替,这将工作得很好.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字段一样工作,但它应该在大多数情况下工作得很好.