问题 blittable类型的非blittable错误


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

[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数据

看完之后 这个MSDN页面 话

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

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

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

我不明白我做错了什么。 我不只是想用 Marshal,但要理解这一点。

所以我真正想知道的是:

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

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


6182
2018-03-21 10:23


起源



答案:


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

这是你得到的异常信息。你关注的是消息中的“不可忽略”部分,但这不是问题所在。这是问题的“非原始”部分。数组是非原始数据类型。

CLR正试图让你摆脱困境。你可以固定物体然后你 仍然 有问题,数组不会被固定。当一个对象具有需要固定的字段时,它不会被真正固定。

而且你对UnmanagedType.ByValArray有一个更大的问题,那就是需要进行结构转换。换句话说,您需要的布局与托管类对象的布局完全不同。只有pinvoke marshaller可以进行此转换。

你可以通过使用固定大小的缓冲区,而不使用pinvoke marshaller来获得你想要的东西 固定 关键词。这需要使用 不安全 关键词。看起来像这样:

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

请注意,您必须将声明更改为a 结构 类型。它现在是一个值类型,当您将其作为局部变量时,不再需要使用GCHandle来固定该值。确保任何非托管代码通常通过引用获取结构值  存储指向结构的指针。这将会严重爆炸,完全不可抗拒。该 不安全 关键字在这里是合适的如果它确实存储了指针,那么你真的必须对项目符号进行字节化并使用Marshal.AllocHGlobal()和Marshal.StructureToPtr()来确保指针在非托管代码使用时保持有效。


11
2018-03-21 14:15



+1 - 很棒的解释,汉斯! - JerKimball


答案:


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

这是你得到的异常信息。你关注的是消息中的“不可忽略”部分,但这不是问题所在。这是问题的“非原始”部分。数组是非原始数据类型。

CLR正试图让你摆脱困境。你可以固定物体然后你 仍然 有问题,数组不会被固定。当一个对象具有需要固定的字段时,它不会被真正固定。

而且你对UnmanagedType.ByValArray有一个更大的问题,那就是需要进行结构转换。换句话说,您需要的布局与托管类对象的布局完全不同。只有pinvoke marshaller可以进行此转换。

你可以通过使用固定大小的缓冲区,而不使用pinvoke marshaller来获得你想要的东西 固定 关键词。这需要使用 不安全 关键词。看起来像这样:

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

请注意,您必须将声明更改为a 结构 类型。它现在是一个值类型,当您将其作为局部变量时,不再需要使用GCHandle来固定该值。确保任何非托管代码通常通过引用获取结构值  存储指向结构的指针。这将会严重爆炸,完全不可抗拒。该 不安全 关键字在这里是合适的如果它确实存储了指针,那么你真的必须对项目符号进行字节化并使用Marshal.AllocHGlobal()和Marshal.StructureToPtr()来确保指针在非托管代码使用时保持有效。


11
2018-03-21 14:15



+1 - 很棒的解释,汉斯! - JerKimball


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

使用固定阵列的两种选择是

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

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

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


3
2018-03-21 14:50





我从这个链接中得到了以下答案(这里

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

0
2018-04-13 15:18