13

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

[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 的解决方案。

4

3 回答 3

16

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

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

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

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

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

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

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

于 2013-03-21T14:15:37.360 回答
4

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

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

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

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

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

于 2013-03-21T14:50:50.010 回答
1

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

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);
于 2017-04-13T15:18:58.840 回答