首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 以檔案流方式讀寫結構體:Marshal 序列化與記憶體配置實戰

     編輯:關於C#

問題背景

處理 .NET Micro Framework 字型檔(tinyfnt)時,檔頭包含固定結構描述資料。期望像 C/C++ 直接將檔案內容讀入結構體,避免逐欄位解析。然而 C# 無直接指令支援,需結合 StructLayout、Marshal 類別實現。

結構體記憶體配置控制

透過屬性自訂結構在記憶體中的排列:


[StructLayout(LayoutKind.Explicit)]
struct TestUnion
{
    [FieldOffset(0)] public int i;
    [FieldOffset(0)] public double d;
    [FieldOffset(0)] public char c;
    [FieldOffset(0)] public byte b;
}
// 所有欄位共用相同記憶體位址,類似 C++ union

[StructLayout(LayoutKind.Explicit)]
struct TestExplicit
{
    [FieldOffset(0)] public long lg;
    [FieldOffset(0)] public int i1;
    [FieldOffset(4)] public int i2;
    [FieldOffset(8)] public double d;
    [FieldOffset(12)] public char c;
    [FieldOffset(14)] public byte b;
}
// 明確指定每個欄位偏移量

平台呼叫(P/Invoke)時此類控制極為重要。

完整讀寫範例程式


using System;
using System.IO;
using System.Runtime.InteropServices;

namespace RWFile
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct TestStruct
    {
        [MarshalAs(UnmanagedType.R8)] public double dTest;
        [MarshalAs(UnmanagedType.U2)] public UInt16 uTest;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] bTest;
    }

    public static class StructIO
    {
        // 序列化:結構體 → byte[]
        public static byte[] RawSerialize(object obj)
        {
            int size = Marshal.SizeOf(obj);
            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(obj, ptr, false);
            byte[] data = new byte[size];
            Marshal.Copy(ptr, data, 0, size);
            Marshal.FreeHGlobal(ptr);
            return data;
        }

        // 反序列化:byte[] → 結構體
        public static T RawDeserialize(byte[] data) where T : struct
        {
            int size = Marshal.SizeOf(typeof(T));
            if (size > data.Length) return default;
            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.Copy(data, 0, ptr, size);
            object obj = Marshal.PtrToStructure(ptr, typeof(T));
            Marshal.FreeHGlobal(ptr);
            return (T)obj;
        }
    }

    // 使用範例
    class Program
    {
        static void Main()
        {
            string path = "test.dat";
            // 寫入
            TestStruct ts = new TestStruct { dTest = 3.14, uTest = 42, bTest = System.Text.Encoding.Default.GetBytes("Hello") };
            File.WriteAllBytes(path, StructIO.RawSerialize(ts));
            // 讀取
            TestStruct ts2 = StructIO.RawDeserialize(File.ReadAllBytes(path));
            Console.WriteLine($"dTest={ts2.dTest}, uTest={ts2.uTest}, bTest={System.Text.Encoding.Default.GetString(ts2.bTest)}");
        }
    }
}

注意事項與限制

  • 結構定義必須與二進位資料完全對應,欄位順序、大小、對齊、字串編碼皆需一致,否則會擲回「記憶體不可讀」等異常。
  • LayoutKind.Sequential 依宣告順序配置,配合 MarshalAs 指定非受控型別對應。
  • 陣列欄位需用 ByValArray 固定大小(SizeConst)。
  • 此法適用於固定長度、無巢狀參考型別的結構;複雜物件圖建議改用標準序列化(BinaryFormatter、JSON、Protobuf)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved