在與 C/C++ DLL 互操作、共享記憶體、網路封包組裝、硬體通訊等場景,必須確保結構體在記憶體中的位元組佈局與非託管端完全一致。C# 預設的序列化器(BinaryFormatter、JSON、Protobuf)皆不保證記憶體佈局,故需使用 System.Runtime.InteropServices.Marshal 進行精確控制。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct DataPacket
{
public int MessageID; // 4 bytes
public double Price; // 8 bytes
public long Quantity; // 8 bytes
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
public string Symbol; // 固定 20 字節 ANSI 字串
// 若需字元陣列可改用:
// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
// public char[] Symbol;
}
屬性解析:
LayoutKind.Sequential:按宣告順序依序排列,配合 Pack = 1 取消對齊填充,確保緊湊佈局。CharSet.Ansi:字串以 ANSI 編碼(單字節)封送,避免 Unicode 雙字節導致大小不符。MarshalAs(ByValTStr, SizeConst=20):固定長度字串,直接內嵌於結構體中,不另行配置堆記憶體。public static byte[] Serialize(T structure) where T : struct
{
int size = Marshal.SizeOf(); // 取得非託管大小
IntPtr ptr = Marshal.AllocHGlobal(size); // 非託管堆配置記憶體
try
{
Marshal.StructureToPtr(structure, ptr, false); // 複製結構體到非託管記憶體
byte[] bytes = new byte[size];
Marshal.Copy(ptr, bytes, 0, size); // 複製到託管位元組陣列
return bytes;
}
finally
{
Marshal.FreeHGlobal(ptr); // 必須釋放非託管記憶體
}
}
public static T Deserialize(byte[] data) where T : struct
{
int size = Marshal.SizeOf();
if (data.Length < size) throw new ArgumentException("資料長度不足");
IntPtr ptr = Marshal.AllocHGlobal(size);
try
{
Marshal.Copy(data, 0, ptr, size); // 託管 → 非託管
return Marshal.PtrToStructure(ptr); // 非託管 → 結構體
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
// 寫入端
var packet = new DataPacket
{
MessageID = 1,
Price = 1.23,
Quantity = 100,
Symbol = "AAPL"
};
byte[] raw = Serialize(packet);
SharedMemory.Write(raw, raw.Length); // 假設的共享記憶體類別
// 讀取端
byte[] buffer = new byte[Marshal.SizeOf()];
SharedMemory.Read(ref buffer);
var recv = Deserialize(buffer);
Console.WriteLine($"{recv.MessageID},{recv.Quantity},{recv.Price},{recv.Symbol}");
byte[] 與 IntPtr,避免頻繁 GC 壓力。MemoryMarshal.Cast 實現零複製轉換。fixed char[] 或 byte[] 承載固定長度文本,減少封送開銷。| 問題 | 原因 | 對策 |
|---|---|---|
| 大小不符 | Pack/CharSet/SizeConst 設定錯誤 | 用 Marshal.SizeOf 對比 C++ sizeof |
| 中文亂碼 | CharSet.Ansi 無法容納 Unicode | 改用 CharSet.Unicode + SizeConst=40(雙字節) |
| 記憶體洩漏 | 忘記 FreeHGlobal | 統一用 try/finally 或 SafeHandle 封裝 |
| 對齊導致位移 | 預設 Pack=8 在 64 位元下插入填充 | 明確指定 Pack = 1 或與非託管端一致 |
掌握 StructLayout、MarshalAs、Marshal 三大支柱,即可在 C# 中實現與 C/C++ 完全相容的記憶體佈局,滿足高效能互操作、共享記憶體、硬體通訊等嚴苛需求。本文提供的泛型輔助方法可直接落地專案,並作為更複雜協議(如自定義二進位協議、零複製網路框架)的基石。