【问题标题】:C# array within a struct结构中的 C# 数组
【发布时间】:2020-09-02 08:22:38
【问题描述】:

想要这样做: (编辑:错误的示例代码,忽略并跳过下面)

struct RECORD {
    char[] name = new char[16];
    int dt1;
}
struct BLOCK {
    char[] version = new char[4];
    int  field1;
    int  field2;
    RECORD[] records = new RECORD[15];
    char[] filler1 = new char[24];
}

但无法在 struct 中声明数组大小,我该如何重新配置​​?

编辑:布局的原因是我使用 BinaryReader 来读取用 C 结构编写的文件。使用 BinaryReader 和 C# struct union (FieldOffset(0)),我想将标头加载为字节数组,然后按照最初的预期读取它。

[StructLayout(LayoutKind.Sequential)]
unsafe struct headerLayout
{
    [FieldOffset(0)]
    char[] version = new char[4];
    int fileOsn;
    int fileDsn;
    // and other fields, some with arrays of simple types
}

[StructLayout(LayoutKind.Explicit)]
struct headerUnion                  // 2048 bytes in header
{
    [FieldOffset(0)]
    public byte[] headerBytes;      // for BinaryReader
    [FieldOffset(0)]
    public headerLayout header;     // for field recognition
}

【问题讨论】:

  • @Joren,为什么不将其添加为答案?
  • 你知道 C# char 是 2 个字节,而 C char 通常是 1 个字节,对吧?
  • 是的,我遇到了,在声明中添加了 Pack=1。好电话!
  • @RobertKerr 我认为Pack=1 不会解决这个问题。 Pack 影响字段之间的填充,而不是 char 的大小。

标签: c#


【解决方案1】:

使用fixed size buffers:

[StructLayout(LayoutKind.Explicit)]
unsafe struct headerUnion                  // 2048 bytes in header
{
    [FieldOffset(0)]
    public fixed byte headerBytes[2048];      
    [FieldOffset(0)]
    public headerLayout header; 
}

您可以只使用该结构并通过以下扩展方法读取它:

private static T ReadStruct<T>(this BinaryReader reader)
        where T : struct
{
    Byte[] buffer = new Byte[Marshal.SizeOf(typeof(T))];
    reader.Read(buffer, 0, buffer.Length);
    GCHandle handle = default(GCHandle);
    try
    {
        handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
    }
    finally
    {
        if (handle.IsAllocated) 
            handle.Free();
    }
}

【讨论】:

  • 固定大小的缓冲区适用于简单类型,但我的一个块读取包含一个包含 22 条记录的数组,每条记录 92 字节。该记录包含 18 个字段。 “固定大小的缓冲区类型必须是以下之一:bool、byte、short、int、long、char、sbyte、ushort、uint、ulong、float 或 double”但是由于固定,我更接近整个解决方案。跨度>
  • Felix,但是如果我在结构中有结构,如果我使用您的通用ReadStruct,我如何获得内部结构?
  • @KonstantinK 如果它只是另一个结构中的普通结构,那么上面的代码应该可以工作。 Marshal.SizeOf(typeof(T)) 计算整个结构体的大小,包括里面的所有结构体。
  • 谢谢你,菲利克斯!我会努力做到的!
  • 不幸的是它不起作用...我创建了单独的主题stackoverflow.com/questions/20246459/…。明天我将开放赏金......也许你可以提出任何解决方案......
【解决方案2】:

非托管结构可以包含嵌入式数组。默认情况下,这些嵌入的数组字段被编组为 SAFEARRAY。在以下示例中,s1 是一个嵌入式数组,直接分配在结构本身内。

Unmanaged representation
struct MyStruct {
    short s1[128];
}

可以将数组编组为 UnmanagedType.ByValArray,这需要您设置 MarshalAsAttribute.SizeConst 字段。大小只能设置为常数。以下代码显示了 MyStruct 的相应托管定义。 C#VB

[StructLayout(LayoutKind.Sequential)]
public struct MyStruct {
   [MarshalAs(UnmanagedType.ByValArray, SizeConst=128)] public short[] s1;
}

【讨论】:

  • 我注意到使用Marshal.SizeOf 这个结构确实包装了sizeof(short) * 128 字节。但这段记忆在哪里?我试图在不拉取非托管数组的情况下做到这一点。我只想使用您在第二个示例中分配的内存,但是当我尝试访问它时,该数组声称是一个空引用。
【解决方案3】:

我一开始不会使用那种模式。这种内存映射可能适用于 c,但不适用于 C# 等高级语言。

我只是为我想读取的每个成员编写一个对二进制读取器的调用。这意味着您可以使用类并以干净的高级方式编写它们。

它还处理字节序问题。而内存映射在不同的字节序系统上使用时会中断。

相关问题:Casting a byte array to a managed structure


因此您的代码将类似于以下内容(添加访问修饰符等):

class Record
{
    char[] name;
    int dt1;
}
class Block {
    char[] version;
    int  field1;
    int  field2;
    RECORD[] records;
    char[] filler1;
}

class MyReader
{
    BinaryReader Reader;

    Block ReadBlock()
    {
        Block block=new Block();
        block.version=Reader.ReadChars(4);
        block.field1=Reader.ReadInt32();
        block.field2=Reader.ReadInt32();
        block.records=new Record[15];
        for(int i=0;i<block.records.Length;i++)
            block.records[i]=ReadRecord();
        block.filler1=Reader.ReadChars(24);
        return block;
    }

    Record ReadRecord()
    {
        ...
    }

    public MyReader(BinaryReader reader)
    {
        Reader=reader;
    }
}

【讨论】:

  • 如果我不读取一个包含 22 个结构的数组(包含 18 个字段)的未知数量的 2K 块的数组,那看起来会更简单。即使只有 1000 条记录,也需要 18000 行代码来读取记录中的每个字段。也许我误解了你的意思。
  • 1000 条记录不会有 18000 行代码。您将有一个循环处理 1000 条记录,循环主体中有 18 行代码。
  • 每个字段有 1-2 行代码。但是您不需要布局属性,因此代码不会更长。但它将是可验证的、安全的、更便携和更清洁的。读取数组时,您显然会使用循环。
  • 我喜欢你的名字。这当然是我有时的感受。还有你的解决方案,比我尝试的要直接得多。
【解决方案4】:

使用不安全的代码和固定大小的缓冲区可以做到这一点:http://msdn.microsoft.com/en-us/library/zycewsya.aspx

固定大小的缓冲区是结构的内联字节。它们不像您的 char[] 那样存在于单独的数组中。

【讨论】:

    【解决方案5】:

    除非你真的需要一个结构体,你可以用一个类来做到这一点。类基本上是一个结构体,使用方式完全相同,但它可以在内部包含方法。这些方法之一是构造函数,一旦您使用“new”创建一个新实例,它将在其中初始化默认值。要创建构造函数,请在其中放置一个与类同名的方法。如果你愿意,它可以接收参数。

    class RECORD 
    {  
    public int dt1;
    public char[] name; 
    public RECORD => name = new char[16] // if it is one-line the {} can be =>
    }
    
    class BLOCK 
        {
        public char[] version;
        public int  field1;
        public int  field2;
        public RECORD[] records;
        public char[] filler1;
        public BLOCK() 
            {
            records = new RECORD[15];
            filler1 = new char[24];
            version = new char[4];
            }      
        }
    

    这样当你创建一个 BLOCK 类型的新项目时,它会被预初始化:

    var myblock = new BLOCK(); 
    Console.WriteLine(myblock.records.Length); // returns 15
    Console.WriteLine(myblock.records[0].Length); // returns 16
    Console.WriteLine(myblock.filler1.Length); // returns 24
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-07
      • 2013-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多