はじめに
今回はStructLayout
とLayoutKind.Explicit
を用いて指定したメモリ領域を別の型として解釈する方法について紹介したいと思います。
別の型として解釈する
StructLayoutAttribute
とLayoutKind.Explicit
を上手に組み合わせることで、本来はキャストできないような別な型として解釈させることができます。
// サンプル : bool(1byte) + byte(1byte) + ushort(2byte) => int(4byte) [StructLayout(LayoutKind.Explicit)] public struct SampleStruct { [FieldOffset(0)] public bool A; [FieldOffset(1)] public byte B; [FieldOffset(2)] public ushort C; [FieldOffset(0)] public int Value; } public static class Program { public static void Main(string[] args) { var sample = new SampleStruct() { A = true, // 1 B = 255, // 255 C = 1000, // 232, 3 }; // 65601281 Console.WriteLine(sample.Value); // 1, 255, 232, 2 Console.WriteLine(string.Join(",", BitConverter.GetBytes(sample.Value))); } }
StructLayoutとは
そもそもStructLayout
とは何だろうということで概要を載せておきます。
メモリ内のクラスまたは構造体のデータ フィールドの物理的なレイアウトを制御できます。
StructLayoutAttribute クラス (System.Runtime.InteropServices) | Microsoft Learn
namespace System.Runtime.InteropServices { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)] public sealed class StructLayoutAttribute : Attribute { public StructLayoutAttribute(LayoutKind layoutKind) { Value = layoutKind; } public StructLayoutAttribute(short layoutKind) { Value = (LayoutKind)layoutKind; } public LayoutKind Value { get; } public int Pack; public int Size; public CharSet CharSet; } }
メモリレイアウト方式を指定できるというわけですね。引数にはLayoutKind
を取ります。
namespace System.Runtime.InteropServices { // Used in the StructLayoutAttribute class public enum LayoutKind { Sequential = 0, Explicit = 2, Auto = 3, } }
Auto(3)
:アンマネージ メモリ内のオブジェクトのメンバーに対して適切なレイアウトを自動的に選択します。Explicit(2)
:各メンバーはFieldOffsetAttribute
を使用して、その型内のフィールドの位置を指定する必要があります。Sequential(0)
:アンマネージ メモリにエクスポートするときに表示される順番に従ってレイアウトされます。 メンバーは、Pack
で指定したパッキングに従ってレイアウトされます。
詳細は未確認飛行さんの記事がわかりやすかったです。
Unsafe.As
またUnsafe.As
を利用することで、指定したメモリ領域を別の型として解釈することができます。
public static ref TTo As<TFrom,TTo> (ref TFrom source);
指定したマネージド ポインターを、 型の値への新しいマネージド ポインターとして再解釈します。
// Sample型のオブジェクトをSample2型として解釈するサンプル [StructLayout(LayoutKind.Explicit)] public struct Sample { [FieldOffset(0)] public bool A; [FieldOffset(1)] public byte B; [FieldOffset(2)] public ushort C; } public struct Sample2 { public int Value; } public static class Program { public static void Main(string[] args) { var sample = new Sample() { A = true, // 1 B = 255, // 255 C = 1000, // 232, 3 }; var sample2 = Unsafe.As<Sample, Sample2>(ref sample); // 65601281 Console.WriteLine(sample2.Value); // 1, 255, 232, 3 Console.WriteLine(string.Join(",", BitConverter.GetBytes(sample2.Value))); } }