.NET Frameworkでは属性によって構造体(またはクラス)のフィールドのレイアウトを指定することができます。 例えばFieldOffset属性を使用すれば各フィールドのオフセットを明示的に指定することができます。 また、StructLayout属性を使用すれば構造体のアラインメント(パッキングサイズ)を指定することができます。 アンマネージAPI呼び出しや、構造を持ったバイナリデータを扱う場合にはStructLayout属性とFieldOffset属性が非常に役立ちます。
概略
StructLayout属性とFieldOffset属性を組み合わせることで、構造体のアラインメント、サイズやフィールドのレイアウトを厳密に定義することができます。 まずは、これらの属性で具体的にどのようなことが出来るか、例を挙げて見ていきます。
C#やVBでは言語要素として共用体(union
)を作成する機能は用意されていません。 しかし、構造体内のフィールドのレイアウトを指定するStructLayout属性と、各フィールドのオフセットを指定するFieldOffset属性を使うことで共用体と同等の機能を持つ構造体を作成することができます。
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
struct DWORD {
// ダブルワード(オフセット0から4バイト分)を格納・参照するフィールド
[FieldOffset(0)] public uint Value;
// 下位ワード(オフセット0から2バイト分)を格納・参照するフィールド
[FieldOffset(0)] public ushort LoWord;
// 上位ワード(オフセット2から2バイト分)を格納・参照するフィールド
[FieldOffset(2)] public ushort HiWord;
}
Imports System
Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Explicit)> _
Structure DWORD
' ダブルワード(オフセット0から4バイト分)を格納・参照するフィールド
<FieldOffset(0)> Public Value As UInteger
' 下位ワード(オフセット0から2バイト分)を格納・参照するフィールド
<FieldOffset(0)> Public LoWord As UShort
' 上位ワード(オフセット2から2バイト分)を格納・参照するフィールド
<FieldOffset(2)> Public HiWord As UShort
End Structure
同様に、#pragma pack(n)
のようなアラインメント(パッキングサイズ)を指定する命令も言語やコンパイラの機能としては用意されていませんが、StructLayout属性のPackフィールドによって指定することができます。
using System;
using System.Runtime.InteropServices;
// 4バイトアラインメント
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct S {
byte F1; // 0〜3バイト目の領域を専有するフィールド (格納・参照できるのは1バイト分)
int F2; // 4〜7バイト目の領域を専有するフィールド (格納・参照できるのは4バイト分)
}
Imports System
Imports System.Runtime.InteropServices
' 4バイトアラインメント
<StructLayout(LayoutKind.Sequential, Pack := 4)> _
Structure S
Dim F1 As Byte ' 0〜3バイト目の領域を専有するフィールド (格納・参照できるのは1バイト分)
Dim F2 As Integer ' 4〜7バイト目の領域を専有するフィールド (格納・参照できるのは4バイト分)
End Structure
また、StructLayout属性のSizeフィールドによってパッキングサイズとは個別に構造体のサイズを指定することができます。
using System;
using System.Runtime.InteropServices;
// 構造体のサイズに2を指定
[StructLayout(LayoutKind.Sequential, Size = 2)]
struct S {
byte F;
}
Imports System
Imports System.Runtime.InteropServices
' 構造体のサイズに2を指定
<StructLayout(LayoutKind.Sequential, Size := 2)> _
Structure S
Dim F As Byte
End Structure
以降で個々の属性や指定できる値について詳しく解説します。
フィールドのレイアウト (StructLayout属性)
StructLayout属性は、メモリ上でのフィールド(メンバ変数)の配置方法を指定するための属性です。 StructLayout属性は構造体だけでなくクラスにも適用することができます。 フィールドの配置方法はLayoutKind列挙体で指定することができ、次のいずれかを指定することが出来ます。
- LayoutKind.Auto
- ランタイムが自動的に最適な順序でフィールドを配置する (StructLayout属性を指定しない場合と同じ)
- LayoutKind.Sequential
- ランタイムによる自動的な並べ替えを行わず、コード上で記述されている順序のままフィールドを配置する
- LayoutKind.Explicit
- 明示的に位置(オフセット)を指定してフィールドを配置する (各フィールドのオフセットを後述のFieldOffset属性で指定する)
構造体にStructLayout属性を適用する場合の例は次のようになります。
using System;
using System.Runtime.InteropServices;
// フィールドのレイアウトを特に指定しない構造体
// (構造体内のフィールドはランタイムによって最適な位置に配置される
// そのため、フィールドの型や数によっては記述されている順序と実際の配置が異なる場合もありうる)
struct S1 {
int F1;
int F2;
}
// フィールドのレイアウトを特に指定しない構造体 (上記の構造体S1と同等)
[StructLayout(LayoutKind.Auto)]
struct S2 {
int F1;
int F2;
}
// StructLayoutにLayoutKind.Sequentialを指定した構造体
// (構造体内のフィールドは記述されたままの順序、つまりこの構造体S3ではF1→F2の順に配置される)
[StructLayout(LayoutKind.Sequential)]
struct S3 {
int F1;
int F2;
}
// StructLayoutにLayoutKind.Explicitを指定した構造体
// (構造体内のフィールドはFieldOffsetで指定されたオフセットに配置される
// この例ではF1は構造体の先頭から0バイト、F2は先頭から4バイトの位置に配置される)
[StructLayout(LayoutKind.Explicit)]
struct S4 {
[FieldOffset(0)] int F1;
[FieldOffset(4)] int F2;
}
Imports System
Imports System.Runtime.InteropServices
' フィールドのレイアウトを特に指定しない構造体
' (構造体内のフィールドはランタイムによって最適な位置に配置される
' そのため、フィールドの型や数によっては記述されている順序と実際の配置が異なる場合もありうる)
Structure S1
Dim F1 As Integer
Dim F2 As Integer
End Structure
' フィールドのレイアウトを特に指定しない構造体 (上記の構造体S1と同等)
<StructLayout(LayoutKind.Auto)> _
Structure S2
Dim F1 As Integer
Dim F2 As Integer
End Structure
' StructLayoutにLayoutKind.Sequentialを指定した構造体
' (構造体内のフィールドは記述されたままの順序、つまりこの構造体S3ではF1→F2の順に配置される)
<StructLayout(LayoutKind.Sequential)> _
Structure S3
Dim F1 As Integer
Dim F2 As Integer
End Structure
' StructLayoutにLayoutKind.Explicitを指定した構造体
' (構造体内のフィールドはFieldOffsetで指定されたオフセットに配置される
' この例ではF1は構造体の先頭から0バイト、F2は先頭から4バイトの位置に配置される)
<StructLayout(LayoutKind.Explicit)> _
Structure S4
<FieldOffset(0)> Dim F1 As Integer
<FieldOffset(4)> Dim F2 As Integer
End Structure
LayoutKind.Explicit
を指定した場合、すべてのフィールドに対してFieldOffset属性を指定する必要があります。 また、アンマネージAPI呼び出しの引数として渡される構造体(またはクラス)には、LayoutKind.Sequential
またはLayoutKind.Explicit
のどちらかが指定されている必要があります。
フィールドのオフセット (FieldOffset属性)
FieldOffset属性は構造体(またはクラス)内における各フィールドの位置(オフセット)を指定するための属性です。 構造体にLayoutKind.Explicit
を指定した場合にはすべてのフィールドに対してFieldOffset属性を指定し、オフセットを明示的に指定する必要があります。 この属性では、構造体の先頭からのオフセット値をバイト単位で指定します。
using System;
using System.Runtime.InteropServices;
// フィールドにFieldOffsetを指定した構造体
// (FieldOffsetを指定する場合はLayoutKind.Explicitを指定する必要がある)
[StructLayout(LayoutKind.Explicit)]
struct S1 {
[FieldOffset(0)] int F1; // このフィールドは構造体の先頭から0バイトの位置に配置される
[FieldOffset(4)] int F2; // このフィールドは構造体の先頭から4バイトの位置に配置される
}
[StructLayout(LayoutKind.Explicit)]
struct S2 {
// フィールド同士を不連続に配置する(フィールドから参照されない領域を作る)こともできる
[FieldOffset(4)] int F1;
[FieldOffset(12)] int F2;
}
Imports System
Imports System.Runtime.InteropServices
' フィールドにFieldOffsetを指定した構造体
' (FieldOffsetを指定する場合はLayoutKind.Explicitを指定する必要がある)
<StructLayout(LayoutKind.Explicit)> _
Structure S1
<FieldOffset(0)> Dim F1 As Integer ' このフィールドは構造体の先頭から0バイトの位置に配置される
<FieldOffset(4)> Dim F2 As Integer ' このフィールドは構造体の先頭から4バイトの位置に配置される
End Structure
<StructLayout(LayoutKind.Explicit)> _
Structure S2
' フィールド同士を不連続に配置する(フィールドから参照されない領域を作る)こともできる
<FieldOffset(4)> Dim F1 As Integer
<FieldOffset(12)> Dim F2 As Integer
End Structure
FieldOffset属性を指定している場合でも、フィールドの値の参照や設定は通常のフィールドと同じように行うことができます。
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
struct S1 {
[FieldOffset(0)] public int F1;
[FieldOffset(4)] public int F2;
}
class Sample {
static void Main()
{
S1 s = new S1();
// 各フィールドに値を設定
s.F1 = 0x00112233;
s.F2 = 0x44556677;
// 各フィールドの値を表示
Console.WriteLine("s.F1 = 0x{0:X8}", s.F1);
Console.WriteLine("s.F2 = 0x{0:X8}", s.F2);
}
}
Imports System
Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Explicit)> _
Structure S1
<FieldOffset(0)> Public F1 As Integer
<FieldOffset(4)> Public F2 As Integer
End Structure
Class Sample
Shared Sub Main()
Dim s As New S1()
' 各フィールドに値を設定
s.F1 = &H00112233
s.F2 = &H44556677
' 各フィールドの値を表示
Console.WriteLine("s.F1 = 0x{0:X8}", s.F1)
Console.WriteLine("s.F2 = 0x{0:X8}", s.F2)
End Sub
End Class
s.F1 = 0x00112233 s.F2 = 0x44556677
この結果にもあるとおり通常の構造体の場合と何ら変わりありません。 しかし、メモリ上の配置は次の図のようになっているはずです。 (図はリトルエンディアン環境でのものです)
オフセット (バイト) |
フィールド | 値 |
---|---|---|
0 | F1 | 0x33 |
1 | 0x22 | |
2 | 0x11 | |
3 | 0x00 | |
4 | F2 | 0x77 |
5 | 0x66 | |
6 | 0x55 | |
7 | 0x44 |
共用体の実装
FieldOffset属性では、他のフィールドと同じオフセットを指定することもできます。 つまり、複数のフィールドが同一のメモリ領域を参照するようにオフセットを指定することができます。 これにより、FieldOffset属性を使って共用体(union
)と同じ構造を作ることができます。 C#やVBでは共用体を直接作成する言語機能はありませんが、構造体とFieldOffset属性を組み合わせることによって共用体となる構造体をつくることができます。
例として上位ワード(2バイト)と下位ワードを参照するフィールドと、ダブルワード(4バイト)を参照するフィールドを持つ共用体を作成すると次のようになります。
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
struct DWORD {
// ダブルワード(オフセット0から4バイト分)を格納・参照するフィールド
[FieldOffset(0)] public uint Value;
// 下位ワード(オフセット0から2バイト分)を格納・参照するフィールド
[FieldOffset(0)] public ushort LoWord;
// 上位ワード(オフセット2から2バイト分)を格納・参照するフィールド
[FieldOffset(2)] public ushort HiWord;
}
class Sample {
static void Main()
{
DWORD dw = new DWORD();
// ダブルワード値を設定
dw.Value = 0x11223344;
// 設定したダブルワード値を表示
Console.WriteLine("Value = 0x{0:X8}", dw.Value);
// 下位ワードと上位ワードの値を表示
Console.WriteLine("LoWord = 0x{0:X4}", dw.LoWord);
Console.WriteLine("HiWord = 0x{0:X4}", dw.HiWord);
}
}
Value = 0x11223344 LoWord = 0x3344 HiWord = 0x1122
Imports System
Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Explicit)> _
Structure DWORD
' ダブルワード(オフセット0から4バイト分)を格納・参照するフィールド
<FieldOffset(0)> Public Value As UInteger
' 下位ワード(オフセット0から2バイト分)を格納・参照するフィールド
<FieldOffset(0)> Public LoWord As UShort
' 上位ワード(オフセット2から2バイト分)を格納・参照するフィールド
<FieldOffset(2)> Public HiWord As UShort
End Structure
Class Sample
Shared Sub Main()
Dim dw As New DWORD()
' ダブルワード値を設定
dw.Value = &H11223344
' 設定したダブルワード値を表示
Console.WriteLine("Value = &H{0:X8}", dw.Value)
' 下位ワードと上位ワードの値を表示
Console.WriteLine("LoWord = &H{0:X4}", dw.LoWord)
Console.WriteLine("HiWord = &H{0:X4}", dw.HiWord)
End Sub
End Class
Value = &H11223344 LoWord = &H3344 HiWord = &H1122
実行結果からも共用体と同等の動作となっていることが分かります。 メモリ上の配置は次のようになっているはずです。
オフセット (バイト) |
フィールド | 値 | |
---|---|---|---|
0 | Value | LoWord | 0x44 |
1 | 0x33 | ||
2 | HiWord | 0x22 | |
3 | 0x11 |
参考までに、これと同等の結果を得るためのC++コードを次に示します。
#include <iostream>
using namespace std;
union DWORD
{
unsigned long Value;
struct
{
unsigned short LoWord;
unsigned short HiWord;
} DWord;
};
int main()
{
DWORD dw;
dw.Value = 0x11223344;
cout << "LoWord = 0x" << hex << dw.DWord.LoWord << endl;
cout << "HiWord = 0x" << hex << dw.DWord.HiWord << endl;
return 0;
}
フィールドのオフセットの取得 (Marshal.OffsetOf)
C/C++のoffsetof()
のように、フィールドのオフセットを取得したい場合はMarshal.OffsetOfメソッドを使用します。 このメソッドはFieldOffset属性で明示的にオフセットを指定していないフィールドでもオフセットを取得することができるため、実行時までオフセットがわからないフィールドのオフセットも取得することができます。
Marshal.OffsetOfメソッドではオフセットを取得したい型をType型、フィールド名を文字列で指定します。 オフセットはIntPtr型としてポインタの形で返されるため、数値として取得したい場合はさらにIntPtr.ToInt32メソッドを呼び出すなどして変換する必要があります。
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
struct S1 {
[FieldOffset(4)] int F1; // オフセットを4に設定したフィールド
}
[StructLayout(LayoutKind.Sequential)]
struct S2 {
byte F1;
int F2; // 1バイトのフィールドの後ろに配置されるフィールド
}
// フィールドの型と数はS2と同じだが、アラインメントを1バイトに指定した構造体
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct S3 {
byte F1;
int F2; // 1バイトのフィールドの後ろに配置されるフィールド
}
class Sample {
static void Main()
{
// 構造体S1のフィールドF1のオフセットを取得する
Console.WriteLine("OffsetOf(S1.F1) = {0}", Marshal.OffsetOf(typeof(S1), "F1").ToInt32());
// 構造体S2のフィールドF2のオフセットを取得する
Console.WriteLine("OffsetOf(S2.F2) = {0}", Marshal.OffsetOf(typeof(S2), "F2").ToInt32());
// 構造体S3のフィールドF2のオフセットを取得する
Console.WriteLine("OffsetOf(S3.F2) = {0}", Marshal.OffsetOf(typeof(S3), "F2").ToInt32());
}
}
Imports System
Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Explicit)> _
Structure S1
<FieldOffset(4)> Dim F1 As Integer ' オフセットを4に設定したフィールド
End Structure
<StructLayout(LayoutKind.Sequential)> _
Structure S2
Dim F1 As Byte
Dim F2 As Integer ' 1バイトのフィールドの後ろに配置されるフィールド
End Structure
' フィールドの型と数はS2と同じだが、アラインメントを1バイトに指定した構造体
<StructLayout(LayoutKind.Sequential, Pack := 1)> _
Structure S3
Dim F1 As Byte
Dim F2 As Integer ' 1バイトのフィールドの後ろに配置されるフィールド
End Structure
Class Sample
Shared Sub Main()
' 構造体S1のフィールドF1のオフセットを取得する
Console.WriteLine("OffsetOf(S1.F1) = {0}", Marshal.OffsetOf(GetType(S1), "F1").ToInt32())
' 構造体S2のフィールドF2のオフセットを取得する
Console.WriteLine("OffsetOf(S2.F2) = {0}", Marshal.OffsetOf(GetType(S2), "F2").ToInt32())
' 構造体S3のフィールドF2のオフセットを取得する
Console.WriteLine("OffsetOf(S3.F2) = {0}", Marshal.OffsetOf(GetType(S3), "F2").ToInt32())
End Sub
End Class
OffsetOf(S1.F1) = 4 OffsetOf(S2.F2) = 4 OffsetOf(S3.F2) = 1
この結果からもわかるように、アラインメントによってフィールドのオフセットが異なる場合もあります。 アラインメントの指定については後述の§.アラインメントの指定 (StructLayoutAttribute.Pack)を参照してください。
.NET Framework 2.0以降では、非パブリックフィールドのオフセットも取得することができます。
.NET Framework 4.5.1以降では、オフセットを取得したい型(Type
)を引数ではなく型パラメータとして指定することもできます。
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
struct S1 {
[FieldOffset(4)] int F1;
}
class Sample {
static void Main()
{
// 構造体S1のフィールドF1のオフセットを取得する
Console.WriteLine("OffsetOf(S1.F1) = {0}", Marshal.OffsetOf<S1>("F1").ToInt32());
}
}
Imports System
Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Explicit)> _
Structure S1
<FieldOffset(4)> Dim F1 As Integer
End Structure
Class Sample
Shared Sub Main()
' 構造体S1のフィールドF1のオフセットを取得する
Console.WriteLine("OffsetOf(S1.F1) = {0}", Marshal.OffsetOf(Of S1)("F1").ToInt32())
End Sub
End Class
OffsetOf(S1.F1) = 4
Marshal.OffsetOfメソッドではフィールド名を文字列で指定するため、取得したいフィールドの名前は既知である必要があります。 名前が未知の任意のフィールドについてオフセットを取得したい場合は、次の例のようにリフレクションによってフィールド名(FieldInfo.Name)を取得してからMarshal.OffsetOfメソッドを呼び出すようにします。
using System;
using System.Reflection;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
struct S {
private byte F1;
public short F2;
public int F3;
}
class Sample {
static void Main()
{
// 構造体Sのすべてのインスタンスフィールドのオフセットを取得して表示する
Type t = typeof(S);
foreach (FieldInfo f in t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) {
Console.WriteLine("OffsetOf(S.{0}) = {1}", f.Name, Marshal.OffsetOf(t, f.Name).ToInt32());
}
}
}
Imports System
Imports System.Reflection
Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Sequential)> _
Structure S
Private F1 As Byte
Public F2 As Short
Public F3 As Integer
End Structure
Class Sample
Shared Sub Main()
' 構造体Sのすべてのインスタンスフィールドのオフセットを取得して表示する
Dim t As Type = GetType(S)
For Each f As FieldInfo In t.GetFields(BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic)
Console.WriteLine("OffsetOf(S.{0}) = {1}", f.Name, Marshal.OffsetOf(t, f.Name).ToInt32())
Next
End Sub
End Class
OffsetOf(S.F1) = 0 OffsetOf(S.F2) = 2 OffsetOf(S.F3) = 4
Type.GetFieldsメソッドについてはリフレクション §.メンバ情報の取得 (MemberInfo)を参照してください。
アラインメントの指定 (StructLayoutAttribute.Pack)
構造体(またはクラス)のフィールドのアラインメント(パッキングサイズ)を変更するにはStructLayout属性でPackフィールドを指定します。 Pack
を指定した場合、各フィールドはPackで指定された値の倍数のオフセットに配置されます。 例えば2を指定すれば各フィールドのオフセットは2の倍数となります。 Pack
に指定できる値は2nである値のうち、0, 1, 2, 4, 8, 16, 32, 64, 128のいずれかです。 0を指定した場合は、デフォルトと同じ、つまりPack
を指定しなかった場合と同じになります。
using System;
using System.Runtime.InteropServices;
// デフォルトのアラインメント
struct S1 {
byte F1;
int F2;
int F3;
}
// 1バイトアラインメント
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct S2 {
byte F1;
int F2;
int F3;
}
// 2バイトアラインメント
[StructLayout(LayoutKind.Sequential, Pack = 2)]
struct S3 {
byte F1;
int F2;
int F3;
}
// 4バイトアラインメント
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct S4 {
byte F1;
int F2;
int F3;
}
class Sample {
static void Main()
{
// 各構造体のフィールドのオフセットを表示する
foreach (var type in new[] {typeof(S1), typeof(S2), typeof(S3), typeof(S4)}) {
foreach (var field in new[] {"F1", "F2", "F3"}) {
Console.WriteLine("OffsetOf({0}.{1}) = {2}", type, field, Marshal.OffsetOf(type, field).ToInt32());
}
Console.WriteLine();
}
}
}
Imports System
Imports System.Runtime.InteropServices
' デフォルトのアラインメント
Structure S1
Dim F1 As Byte
Dim F2 As Integer
Dim F3 As Integer
End Structure
' 1バイトアラインメント
<StructLayout(LayoutKind.Sequential, Pack := 1)> _
Structure S2
Dim F1 As Byte
Dim F2 As Integer
Dim F3 As Integer
End Structure
' 2バイトアラインメント
<StructLayout(LayoutKind.Sequential, Pack := 2)> _
Structure S3
Dim F1 As Byte
Dim F2 As Integer
Dim F3 As Integer
End Structure
' 4バイトアラインメント
<StructLayout(LayoutKind.Sequential, Pack := 4)> _
Structure S4
Dim F1 As Byte
Dim F2 As Integer
Dim F3 As Integer
End Structure
Class Sample
Shared Sub Main()
' 各構造体のフィールドのオフセットを表示する
For Each type As Type In New Type() {GetType(S1), GetType(S2), GetType(S3), GetType(S4)}
For Each field As String In New String() {"F1", "F2", "F3"}
Console.WriteLine("OffsetOf({0}.{1}) = {2}", type, field, Marshal.OffsetOf(type, field).ToInt32())
Next
Console.WriteLine()
Next
End Sub
End Class
OffsetOf(S1.F1) = 0 OffsetOf(S1.F2) = 4 OffsetOf(S1.F3) = 8 OffsetOf(S2.F1) = 0 OffsetOf(S2.F2) = 1 OffsetOf(S2.F3) = 5 OffsetOf(S3.F1) = 0 OffsetOf(S3.F2) = 2 OffsetOf(S3.F3) = 6 OffsetOf(S4.F1) = 0 OffsetOf(S4.F2) = 4 OffsetOf(S4.F3) = 8
この結果からもわかるとおり、各フィールドは次のように配置されているはずです。
オフセット (バイト) |
フィールド |
---|---|
0 |
byte F1
|
1 |
int F2
|
2 | |
3 | |
4 | |
5 |
int F3
|
6 | |
7 | |
8 |
オフセット (バイト) |
フィールド |
---|---|
0 |
byte F1
|
1 | 未使用 |
2 |
int F2
|
3 | |
4 | |
5 | |
6 |
int F3
|
7 | |
8 | |
9 |
オフセット (バイト) |
フィールド |
---|---|
0 |
byte F1
|
1 | 未使用 |
2 | |
3 | |
4 |
int F2
|
5 | |
6 | |
7 | |
8 |
int F3
|
9 | |
10 | |
11 |
なお、.NET FrameworkおよびMonoのデフォルトではアラインメントは4
となるようです。
サイズの指定 (StructLayoutAttribute.Size)
StructLayout属性でSizeフィールドを指定することにより、構造体のサイズを明示的に指定することができます。 例えば、サイズは固定されているが、具体的なフィールドは割り当てられていない構造体を作成したい場合などに使用します。 Packフィールドとは異なり、Size
フィールドには2nだけでなく任意の値を指定することができます。
using System;
using System.Runtime.InteropServices;
// デフォルトのサイズ
struct S1 {
byte F1;
}
// 構造体のサイズに2を指定
[StructLayout(LayoutKind.Sequential, Size = 2)]
struct S2 {
byte F1;
}
// 構造体のサイズに4を指定
[StructLayout(LayoutKind.Sequential, Size = 4)]
struct S3 {
byte F1;
}
// 構造体のサイズに6を指定
[StructLayout(LayoutKind.Sequential, Size = 6)]
struct S4 {
byte F1;
}
class Sample {
static void Main()
{
// 各構造体のサイズを表示する
foreach (var type in new[] {typeof(S1), typeof(S2), typeof(S3), typeof(S4)}) {
Console.WriteLine("SizeOf({0}) = {1}", type, Marshal.SizeOf(type));
}
}
}
Imports System
Imports System.Runtime.InteropServices
' デフォルトのサイズ
Structure S1
Dim F1 As Byte
End Structure
' 構造体のサイズに2を指定
<StructLayout(LayoutKind.Sequential, Size := 2)> _
Structure S2
Dim F1 As Byte
End Structure
' 構造体のサイズに4を指定
<StructLayout(LayoutKind.Sequential, Size := 4)> _
Structure S3
Dim F1 As Byte
End Structure
' 構造体のサイズに6を指定
<StructLayout(LayoutKind.Sequential, Size := 6)> _
Structure S4
Dim F1 As Byte
End Structure
Class Sample
Shared Sub Main()
' 各構造体のサイズを表示する
For Each type As Type In New Type() {GetType(S1), GetType(S2), GetType(S3), GetType(S4)}
Console.WriteLine("SizeOf({0}) = {1}", type, Marshal.SizeOf(type))
Next
End Sub
End Class
SizeOf(S1) = 1 SizeOf(S2) = 2 SizeOf(S3) = 4 SizeOf(S4) = 6
構造体のサイズの取得については構造体のサイズも参照してください。
構造体内で定義されているフィールドの総サイズがSize
フィールドで指定している値を超える場合、構造体のサイズは当然Size
フィールドで指定している値よりも大きくなります。 コンパイルエラーや実行時エラーは発生しないため、意図しない動作の原因となりうることに注意が必要です。
using System;
using System.Runtime.InteropServices;
// 構造体のサイズに2を指定
[StructLayout(LayoutKind.Sequential, Size = 2)]
struct S {
// 4バイト分のフィールドを定義
int F;
}
class Sample {
static void Main()
{
Console.WriteLine("SizeOf(S) = {0}", Marshal.SizeOf(typeof(S)));
}
}
Imports System
Imports System.Runtime.InteropServices
' 構造体のサイズに2を指定
<StructLayout(LayoutKind.Sequential, Size := 2)> _
Structure S
' 4バイト分のフィールドを定義
Dim F As Integer
End Structure
Class Sample
Shared Sub Main()
Console.WriteLine("SizeOf(S) = {0}", Marshal.SizeOf(GetType(S)))
End Sub
End Class
SizeOf(S) = 4