C#编组布尔 [英] C# Marshalling bool

查看:84
本文介绍了C#编组布尔的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

场景

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

This should be an easy task, but for some reason I can't get it going as intended. I have to marshal a basic C++ struct during a reversed-P/Invoke call (unmanaged calling managed code).

仅当在结构体中使用bool时才会出现此问题,因此我只将C ++面修整为:

The issue only arises when using bool within the struct, so I just trim the C++ side down to:

struct Foo {
    bool b;
};

由于默认情况下.NET将布尔值编组为4字节字段,所以我将本地布尔值显式编组为1字节长的字段:

Since .NET marshals booleans as 4-byte fields by default, I marshal the native boolean explicitly as a 1 byte-length field:

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

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

When I call an exported managed static method with the following signature and body:

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

我得到了正确的布尔字母表示形式.如果我将结构扩展为具有更多字段,则对齐方式正确,并且编组后数据不会损坏.

I get the correct boolean alpha-representation printed. If I extend the structure with more fields, the alignment is correct and the data is not corrupt after marshalling.

问题

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

For some reason, if I do not pass this marshalled struct as an argument but rather as a return type by value:

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

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

The application crashes with the following error message:

如果我更改托管结构以容纳byte而不是bool

If I change the managed structure to hold a byte instead of a bool

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

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

返回值被正确地编组,不会对非托管布尔值产生错误.

the return value is marshalled properly without an error to an unmanaged bool.

在这里我不会理解两件事:

I don't unterstand two things here:

  1. 为什么如上所述用bool编组参数,但是作为返回值却给出错误?
  2. 为什么将byte编组为UnmanagedType.I1的商品用于退货,而又​​将bool编组为UnmanagedType.I1的商品却不能退货?
  1. Why does a paramter marshalled with bool as described above work, but as a return value give an error?
  2. Why does a byte marshalled as UnmanagedType.I1 work for returns, but a bool also marshalled with UnmanagedType.I1 does not?

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

I hope my description makes sense -- if not, please let me know so I can change the wording.

我当前的解决方法是一个托管结构,如:

My current workaround is a managed struct like:

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

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

which honestly, I find quite ridiculous...

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

Here is an almost-MCVE. The managed assembly has been recompiled with proper symbol exports (using .export and .vtentry attributes in IL code), but there should be no difference to C++/CLI calls. So this code is not working "as-is" without doing the exports manually:

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

推荐答案

基本问题与此问题相同-

The underlying problem is the same as with this question - Why DllImport for C bool as UnmanagedType.I1 throws but as byte it works The exception you're getting is MarshalDirectiveException - getting the remaining information about the exception is a bit trickier, but unnecessary.

简而言之,将返回值编组仅适用于可漂白结构.当您指定使用布尔值字段时,该结构不再可拆分(因为bool不可拆分),并且不再适用于返回值.这只是编组器的一个限制,它适用于DllImport和您尝试的"DllExport".

In short, marshalling for return values only works for blittable structures. When you specify use a boolean field, the structure is no longer blittable (because bool isn't blittable), and will no longer work for return values. This is simply a limitation of the marshaller, and it applies for both DllImport and your attempts at "DllExport".

引用相关文档片段:

从平台调用调用返回的结构必须是可蓝调的类型.平台调用不支持将不可引用的结构作为返回类型.

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

并没有说完全,但是在调用时同样适用.

It's not said outright, but the same thing applies when being invoked.

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

The simplest workaround is to stick with your "byte as a backing field, bool as a property" approach. Alternatively, you could use the C BOOL instead, which will work just fine. And of course, there's always the option of using a C++/CLI wrapper, or even just hiding the real layout of the structure in your helper methods (in this case, your export methods will call another method that deals with the real Foo type, and handle the proper conversion to the Foo++ type).

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

It's also possible to use a ref argument instead of a return value. This is in fact a common pattern in unmanaged interop:

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#端.

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

You could also make your own boolean type, a simple struct with a single byte field, with implicit cast operators to and from bool - it's not going to work exactly as a real bool field, but it should work just fine most of the time.

这篇关于C#编组布尔的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆