C#互操作:固定和封送之间的不良交互 我需要将C 4中的嵌套结构编成二进制块,以传递给C++框架。

C#互操作:固定和封送之间的不良交互 我需要将C 4中的嵌套结构编成二进制块,以传递给C++框架。,c#,interop,marshalling,unsafe,C#,Interop,Marshalling,Unsafe,到目前为止,我已经成功地使用safe/fixed来处理基元类型的固定长度数组。现在我需要处理一个包含其他结构的嵌套固定长度数组的结构 我使用了复杂的解决方法来扁平化结构,但后来我遇到了一个MarshalAs属性的示例,它看起来可以为我节省很多问题 不幸的是,虽然它提供了正确的数据量,但它似乎也阻止了固定的数组被正确编组,正如该程序的输出所示。您可以通过在最后一行放置断点并检查每个指针处的内存来确认失败 using System; using System.Threading; using Sys

到目前为止,我已经成功地使用
safe
/
fixed
来处理基元类型的固定长度数组。现在我需要处理一个包含其他结构的嵌套固定长度数组的结构

我使用了复杂的解决方法来扁平化结构,但后来我遇到了一个
MarshalAs
属性的示例,它看起来可以为我节省很多问题

不幸的是,虽然它提供了正确的数据量,但它似乎也阻止了
固定的
数组被正确编组,正如该程序的输出所示。您可以通过在最后一行放置断点并检查每个指针处的内存来确认失败

using System;
using System.Threading;
using System.Runtime.InteropServices;

namespace MarshalNested
{
  public unsafe struct a_struct_test1
  {
    public fixed sbyte a_string[3];
    public fixed sbyte some_data[12];
  }

  public struct a_struct_test2
  {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public sbyte[] a_string;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public a_nested[] some_data;
  }

  public unsafe struct a_struct_test3
  {
    public fixed sbyte a_string[3];
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public a_nested[] some_data;
  }


  public unsafe struct a_nested
  {
    public fixed sbyte a_notherstring[3];
  }

  class Program
  {
    static unsafe void Main(string[] args)
    {
      a_struct_test1 lStruct1 = new a_struct_test1();
      lStruct1.a_string[0] = (sbyte)'a';
      lStruct1.a_string[1] = (sbyte)'b';
      lStruct1.a_string[2] = (sbyte)'c';

      a_struct_test2 lStruct2 = new a_struct_test2();
      lStruct2.a_string = new sbyte[3];
      lStruct2.a_string[0] = (sbyte)'a';
      lStruct2.a_string[1] = (sbyte)'b';
      lStruct2.a_string[2] = (sbyte)'c';

      a_struct_test3 lStruct3 = new a_struct_test3();
      lStruct3.a_string[0] = (sbyte)'a';
      lStruct3.a_string[1] = (sbyte)'b';
      lStruct3.a_string[2] = (sbyte)'c';

      IntPtr lPtr1 = Marshal.AllocHGlobal(15);
      Marshal.StructureToPtr(lStruct1, lPtr1, false);

      IntPtr lPtr2 = Marshal.AllocHGlobal(15);
      Marshal.StructureToPtr(lStruct2, lPtr2, false);

      IntPtr lPtr3 = Marshal.AllocHGlobal(15);
      Marshal.StructureToPtr(lStruct3, lPtr3, false);

      string s1 = "";
      string s2 = "";
      string s3 = "";
      for (int x = 0; x < 3; x++)
      {
        s1 += (char) Marshal.ReadByte(lPtr1+x);
        s2 += (char) Marshal.ReadByte(lPtr2+x);
        s3 += (char) Marshal.ReadByte(lPtr3+x);
      }

      Console.WriteLine("Ptr1 (size " + Marshal.SizeOf(lStruct1) + ") says " + s1);
      Console.WriteLine("Ptr2 (size " + Marshal.SizeOf(lStruct2) + ") says " + s2);
      Console.WriteLine("Ptr3 (size " + Marshal.SizeOf(lStruct3) + ") says " + s3);

      Thread.Sleep(10000);
    }
  }
}

因此,出于某种原因,它只是编组我的
fixed
ANSI字符串的第一个字符。有没有办法解决这个问题,或者我做了一些与编组无关的蠢事?

这是一个诊断缺失的案例。应该有人说出来,告诉你你的声明不被支持。其中有人是产生编译错误的C#编译器,或者是产生运行时异常的CLR字段封送器

并不是说你不能得到诊断。当您真正开始按预期使用结构时,您肯定会得到一个:

    a_struct_test3 lStruct3 = new a_struct_test3();
    lStruct3.some_data = new a_nested[4];
    lStruct3.some_data[0] = new a_nested();
    lStruct3.some_data[0].a_notherstring[0] = (sbyte)'a';  // Eek!
这引出了CS1666,“您不能使用非固定表达式中包含的固定大小缓冲区。请尝试使用固定语句”。并不是说“试试这个”建议就那么有帮助:

    fixed (sbyte* p = &lStruct3.some_data[0].a_notherstring[0])  // Eek!
    {
        *p = (sbyte)'a';
    }
完全相同的CS1666错误。接下来要尝试的是在固定缓冲区上放置一个属性:

public unsafe struct a_struct_test3 {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public fixed sbyte a_string[3];
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public a_nested[] some_data;
}
//...

    a_struct_test3 lStruct3 = new a_struct_test3();
    lStruct3.some_data = new a_nested[4];
    IntPtr lPtr3 = Marshal.AllocHGlobal(15);
    Marshal.StructureToPtr(lStruct3, lPtr3, false);  // Eek!
使C#编译器满意,但现在CLR发出声音,您在运行时收到TypeLoadException:“其他信息:无法封送类型为“MarshalNested.a_struct_test3”的字段“a_string”:无效的托管/非托管类型组合(此值类型必须与struct成对出现)。”

所以,简而言之,您在最初的尝试中也应该得到CS1666或TypeLoadException。这并没有发生,因为C#编译器没有被迫查看坏的部分,它只在访问数组的语句上生成CS1666。而且它没有在运行时发生,因为CLR中的字段封送器没有尝试封送数组,因为它为null。你可以在connect.microsoft.com上提交一份bug反馈报告,但如果他们不以“按设计”结束报告,我会非常惊讶


一般来说,一个模糊的细节对CLR中的字段封送器非常重要,CLR是将结构值和类对象从托管布局转换为非托管布局的代码块。它的文档记录很差,微软不想确定具体的实施细节。主要是因为它们过于依赖于目标体系结构

重要的是一个值或对象是否可飞航。当托管布局和非托管布局相同时,它是可空投的。只有当类型的每个成员在两个布局中具有完全相同的大小和对齐方式时,才会发生这种情况。通常,只有当字段是非常简单的值类型(如byte或int)或本身是可Blittta的结构时,才会发生这种情况。臭名昭著的不是当它是bool时,太多冲突的非托管bool类型。数组类型的字段永远不可Blitttable,托管数组看起来不像C数组,因为它们有一个对象头和一个长度成员

具有可blittable值或对象是非常理想的,它避免了字段封送处理程序必须创建副本。本机代码获得一个指向托管内存的简单指针,只需固定内存即可。非常快。这也是非常危险的,如果声明不匹配,那么本机代码很容易在行外着色,并损坏GC堆或堆栈框架。使用pinvoke随机轰炸ExecutionEngineeException程序的一个非常常见的原因,非常难以诊断。这样的声明确实值得使用不安全的关键字,但C#编译器并不坚持这样做。它也不能,编译器不允许对托管对象布局做出任何假设。通过对
Marshal.SizeOf
的返回值使用Debug.Assert()来确保其安全,它必须与C程序中的
SizeOf(T)
的值完全匹配

如前所述,数组是获取可blittable值或对象的障碍。
fixed
关键字用于解决此问题。CLR将其视为一个不透明的值类型,没有成员,只有一个字节块。没有对象头和长度成员,尽可能接近C数组。在C#代码中使用,就像在C程序中使用数组一样,必须使用指针对数组元素进行寻址,并检查三次,确保不在行外着色。有时必须使用固定数组,这是在声明并集(重叠字段)并将数组与值重叠时发生的。对垃圾收集器来说,它无法再确定字段是否存储对象根。C#编译器未检测到,但在运行时可靠地触发TypeLoadException



长话短说,使用
fixed
仅适用于可飞行类型。将固定大小缓冲区类型的字段与必须封送的字段混合使用无法工作。而且没有用,对象或值会被复制,因此您最好使用友好的数组类型。

您可以看到以下答案,例如:,它描述了相同的问题(以及注释)。当我执行这种将某个二进制blob映射到C#的技巧时,我这样做是为了避免封送。我远离任何有“元帅”字样的东西。您可以使用StructLayout.Explicit进行联合,将其放入第一个数组元素中,以通过&someData进行数组样式的访问,并在其上覆盖一个固定字节数组以访问ma
public unsafe struct a_struct_test3 {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public fixed sbyte a_string[3];
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public a_nested[] some_data;
}
//...

    a_struct_test3 lStruct3 = new a_struct_test3();
    lStruct3.some_data = new a_nested[4];
    IntPtr lPtr3 = Marshal.AllocHGlobal(15);
    Marshal.StructureToPtr(lStruct3, lPtr3, false);  // Eek!