【问题标题】:Non-blittable error on a blittable typeblittable 类型上的非 blittable 错误
【发布时间】:2013-03-21 10:23:07
【问题描述】:

我有这个结构和这个代码:

[StructLayout(LayoutKind.Sequential, Pack = 8)]
private class xvid_image_t
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public int[] stride;

    // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    // public IntPtr[] plane;
}

public int decore()
{
    xvid_image_t myStruct = new xvid_image_t();
    myStruct.stride = new int[4]; // can be commented out - same result
    GCHandle.Alloc(myStruct, GCHandleType.Pinned);

    // ...
}

当我尝试运行它时,我得到一个ArgumentException 说:

对象包含非原始数据或非 blittable 数据

看完this MSDN page说的话

以下复杂类型也是 blittable 类型:

  • blittable 类型的一维数组,例如整数数组。但是,包含可变数组的 blittable 类型本身不是 blittable。

  • 仅包含 blittable 类型的格式化值类型(以及类,如果它们被编组为格式化类型)。有关格式化值类型的更多信息,请参阅值类型的默认封送处理。

我不明白我做错了什么。 我不仅要使用Marshal,还要理解这一点。

所以我真正想知道的是:

  1. 为什么?
  2. 我该如何解决这个问题?
  3. 您提供的解决方案是否也适用于结构中的注释行?

我使用的是 .Net 4.5,但也需要 .Net 2.0 的解决方案。

【问题讨论】:

    标签: c# .net struct garbage-collection marshalling


    【解决方案1】:

    对象包含非原始数据或非 blittable 数据

    这是您收到的异常消息。您正在关注消息的“不可blittable”部分,但这不是问题。问题在于“非原始”部分。数组是一种非原始数据类型。

    CLR 试图让您远离这里的麻烦。您可以固定对象,但仍然有问题,数组不会被固定。当一个对象也有需要固定的字段时,它并不是真正固定的。

    UnmanagedType.ByValArray 存在更大的问题,需要进行结构转换。换句话说,您需要的布局与托管类对象的布局完全不同。只有 pinvoke marshaller 才能进行这种转换。

    通过使用固定大小的缓冲区,使用 fixed 关键字,您可以在不使用 pinvoke 编组器的情况下获得所需的内容。这需要使用 unsafe 关键字。让它看起来像这样:

        [StructLayout(LayoutKind.Sequential)]
        unsafe private struct xvid_image_t {
            public fixed int stride[4];
        }
    

    请注意,您必须将声明更改为 struct 类型。它现在是一种值类型,当您将其设为局部变量时,您不再需要使用 GCHandle 来固定该值。请确保无论非托管代码通常通过引用获取结构值,not 是否存储指向该结构的指针。这将严重且完全无法诊断。 unsafe 关键字在这里很合适。如果它确实存储了指针,那么您确实必须对项目符号进行字节处理并使用 Marshal.AllocHGlobal() 和 Marshal.StructureToPtr() 来确保指针在非托管代码使用它时保持有效。

    【讨论】:

      【解决方案2】:

      .NET 的一个令人讨厌的限制是,它识别的唯一数组式事物是独立的 System.Array 对象和 System.String,它们都是引用类型。用 C# 编写的代码可以使用 fixed 数组(如 Hans Passant 所述),但 .NET 本身无法识别这种类型,并且使用固定数组的代码无法验证。此外,固定数组仅限于保存原语,不能被其他语言如 vb.net 访问。

      使用固定数组的两种选择是

      • 用一些字段组合替换固定数组,这些字段组合在一起总大小合适(在大多数情况下使用 N 个变量,但可能会用 UInt32 替换 char[4],或者用 char[8] 替换UInt64)。如果数组不是太大,可以定义(通过剪切/粘贴或反射)一组静态方法,这些方法通过 ref 获取结构并读/写适当的元素,然后创建一个委托数组来调用这些方法.

      • 用数组替换整个结构,然后将该数组的第一个元素作为ref 参数传递。这可能比在结构中使用fixed 数组更“危险”,但这是我在 vb.net 中知道的唯一方法,可以通过包含真正需要的结构的结构获得“传递引用”语义以数组形式访问。

      虽然我可以理解值类型数组可能被认为是“令人困惑的”(特别是如果它们是自动装箱的),但从以下角度来看,它们在某些地方可能是数组存储的语义正确方法从应该返回少量值的方法的角度来看,允许 COM 互操作的 pass-by-ref 语义。例如,在System.Drawing2d 中,有一个方法将当前图形变换作为float[6] 返回;除了通过实验之外,没有明确的方法可以知道返回后对该数组的更改是否会影响、可能影响或保证不会影响其他任何东西。如果该方法返回一个值类型数组,很明显对返回数组的更改不会影响其他任何东西。尽管如此,无论值类型数组是否会成为框架的有用部分,事实仍然是,无论出于好的原因还是坏的原因,都不存在这样的东西。

      【讨论】:

        【解决方案3】:

        我从这个链接(here)得到了以下答案

        SItuLongEmailMsg msg = newSItuLongEmailMsg();
        // set members
        msg.text = new byte[2048];
        // assign to msg.text
        int msgSize = Marshal.SizeOf(msg);
        IntPtr ptr = Marshal.AllocHGlobal(msgSize);
        Marshal.StructureToPtr(msg, ptr, true);
        byte[] dataOut = new byte[msgSize];
        Marshal.Copy(ptr, dataOut, 0, msgSize);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-05-21
          • 1970-01-01
          • 2019-05-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多