blittable类型的非blittable错误

时间:2013-03-21 10:23:07

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

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

[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. 您提供的解决方案是否也可以使用结构中的注释行?
  4. 我使用的是.Net 4.5,但也需要.Net 2.0的解决方案。

3 个答案:

答案 0 :(得分:14)

  

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

这是你得到的异常信息。你正专注于信息的“不可眩晕”部分,但这不是问题。这是问题的“非原始”部分。数组是非原始数据类型。

CLR正试图让你摆脱困境。您可以固定对象,但是仍然有问题,阵列将不会被固定。当对象具有需要固定的字段时,它不会被真正固定。

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

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

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

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

答案 1 :(得分:3)

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

使用固定数组的两种方法是

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

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

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

答案 2 :(得分:1)

我从这个链接(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);