如何将字节块读入struct

时间:2011-08-02 20:59:10

标签: c# .net struct filestream

我有这个需要处理的资源文件,它包含一组文件。

首先,资源文件列出了其中包含的所有文件,以及其他一些数据,例如在此结构中:

struct FileEntry{
     byte Value1;
     char Filename[12];
     byte Value2;
     byte FileOffset[3];
     float whatever;
}

所以我需要读取这个大小的块。

我正在使用FileStream中的Read函数,但是如何指定struct的大小? 我用过:

int sizeToRead = Marshal.SizeOf(typeof(Header));

然后将此值传递给Read,但之后我只能读取一组byte [],我不知道如何转换为指定的值(我知道如何获取单字节值...但不是其他人。)

另外,我需要指定一个不安全的上下文,我不知道它是否正确......

在我看来,读取字节流比我在.NET中想象的更难:)

谢谢!

5 个答案:

答案 0 :(得分:7)

假设这是C#,我不会将结构创建为FileEntry类型。我会用字符串替换char [20]并使用BinaryReader - http://msdn.microsoft.com/en-us/library/system.io.binaryreader.aspx来读取单个字段。您必须按照写入的顺序读取数据。

类似的东西:

class FileEntry {
     byte Value1;
     char[] Filename;
     byte Value2;
     byte[] FileOffset;
     float whatever;
}

  using (var reader = new BinaryReader(File.OpenRead("path"))) {
     var entry = new FileEntry {
        Value1 = reader.ReadByte(),
        Filename = reader.ReadChars(12) // would replace this with string
        FileOffset = reader.ReadBytes(3),
        whatever = reader.ReadFloat()           
     };
  }

如果你坚持使用结构,你应该使你的结构不可变,并为每个字段创建一个带有参数的构造函数。

答案 1 :(得分:5)

如果您可以使用不安全的代码:

unsafe struct FileEntry{
     byte Value1;
     fixed char Filename[12];
     byte Value2;
     fixed byte FileOffset[3];
     float whatever;
}

public unsafe FileEntry Get(byte[] src)
{
     fixed(byte* pb = &src[0])
     {
         return *(FileEntry*)pb;
     } 
}

fixed关键字将数组嵌入struct中。由于它已修复,如果您经常创建这些 会导致GC问题,并且永远不会让它们消失。请记住,常量大小是n * sizeof(t)。所以Filename [12]分配24个字节(每个char是2个字节unicode),FileOffset [3]分配3个字节。如果您不处理磁盘上的unicode数据,这很重要。我建议将它更改为byte []并将结构转换为可以转换字符串的可用类。

如果你不能使用unsafe,你可以做整个BinaryReader方法:

public unsafe FileEntry Get(Stream src)
{
     FileEntry fe = new FileEntry();
     var br = new BinaryReader(src);
     fe.Value1 = br.ReadByte();
     ...
}

这种不安全的方式几乎是即时的,速度要快得多,特别是当你一次转换很多结构时。问题是你想使用不安全的。如果您绝对需要性能提升,我的建议只使用不安全的方法。

答案 2 :(得分:3)

基于this article,只有我将它设为通用,这是如何将数据直接编组到结构中。对于较长的数据类型非常有用。

public static T RawDataToObject<T>(byte[] rawData) where T : struct
{
    var pinnedRawData = GCHandle.Alloc(rawData,
                                       GCHandleType.Pinned);
    try
    {
        // Get the address of the data array
        var pinnedRawDataPtr = pinnedRawData.AddrOfPinnedObject();

        // overlay the data type on top of the raw data
        return (T) Marshal.PtrToStructure(pinnedRawDataPtr, typeof(T));
    }
    finally
    {
        // must explicitly release
        pinnedRawData.Free();
    }
}

示例用法:

[StructLayout(LayoutKind.Sequential)]
public struct FileEntry
{
    public readonly byte Value1;

    //you may need to play around with this one
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
    public readonly string Filename;

    public readonly byte Value2;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public readonly byte[] FileOffset;

    public readonly float whatever;
}

private static void Main(string[] args)
{
    byte[] data =;//from file stream or whatever;
    //usage
    FileEntry entry = RawDataToObject<FileEntry>(data);
}

答案 3 :(得分:2)

使用FileStream包裹BinaryReader将为您提供基本类型的专用Read*()方法: http://msdn.microsoft.com/en-us/library/system.io.binaryreader.aspx

在我的脑海中,您可能会用struct标记[StructLayout(LayoutKind.Sequential)](以确保在内存中正确表示)并使用unsafe块中的指针实际填充结构C-样式。如果你真的不需要它(互操作,像图像处理这样繁重的操作等),不建议使用unsafe

答案 4 :(得分:0)

不是一个完整的答案(我认为它已被覆盖),但有关文件名的具体说明:

Char类型可能不是C#中的单字节内容,因为.Net字符是unicode,这意味着它们支持的字符值远远超过255,因此将文件名数据解释为Char[]数组将给出问题。因此,第一步肯定是将其视为Byte[12],而不是Char[12]

也不建议从字节数组到字符数组的直接转换,因为在这样的二进制索引中,更短的文件名可能会被填充0字节,因此,直接转换将导致字符串总是长度为12个字符,并且可能以这些零字符结束。

但是,建议不要简单地修剪这些零,因为读取此类数据的系统通常只读取到第一个遇到的零,而数组中的后面的数据实际上可能包含垃圾。在将字符串放入其中之前,写入系统并不打算用零来专门清理其缓冲区。这是很多程序都不会做的事情,因为他们认为阅读系统只会将字符串解释为第一个零。

因此,假设这确实是一个典型的零终止(C风格)字符串,保存为每字符一个字节的文本编码(如ASCII或Win-1252),第二步是切断第一个零点上的字符串。您可以使用Linq的TakeWhile功能轻松完成此操作。然后第三步也是最后一步是将生成的字节数组转换为字符串,其中所写的每字符一个字节的文本编码恰好是:

public String StringFromCStringArray(Byte[] readData, Encoding encoding)
{
    return encoding.GetString(readData.TakeWhile(x => x != 0).ToArray());
}

正如我所说,编码可能类似于纯ASCII,可以从Encoding.ASCII或Windows-1252(标准的美国/西欧Windows文本编码)访问,您可以使用{{{ 1}}。