もくじ
https://tera1707.com/entry/2022/02/06/144447
やりたいこと
COMについて勉強していたときに、
[StructLayout(LayoutKind.Explicit)] public struct PROPVARIANT { [FieldOffset(0)] public ushort vt; [FieldOffset(2)] public ushort wReserved1; [FieldOffset(4)] public ushort wReserved2; [FieldOffset(6)] public ushort wReserved3; [FieldOffset(8)] public IntPtr pwszVal; [FieldOffset(8)] public float fltVal; }
みたいなコードが出てきた。
StructLayout
とは何か?調べたい。
前提
- VisualStudio2022
- 2023/10月時点で試したこと
しらべたこと
ufcppの複合型のレイアウトを見ながら勉強させていただいた。ありがとうございます。
StructLayoutはC#で使うものだが、一旦C++でstructを作ったときにどういうアラインメントになるかを見てから、イメージを膨らませてからC#の構造体を作って、StructLayoutを試そうと思う。
アラインメントとは?
下記参照... https://ufcpp.net/study/csharp/interop/memorylayout/
C++でアラインメントを試してみる
下記のようなコードを書いて、どういう配置になってるのか?試してみた。
#include <iostream> #include <string> struct MyStruct { char A; // char=1バイト long B; // long=4バイト char C; // char=1バイト } ; MyStruct ms; int main() { auto pA = &ms.A; auto pB = &ms.B; auto pC = &ms.C; printf("Address A : %x \r\n", (int)pA); printf("Address B : %x \r\n", (int)pB); printf("Address C : %x \r\n", (int)pC); printf("\r\n"); printf("Address B-A : %d \r\n", ((int)pB - (int)pA)); printf("Address C-A : %d \r\n", ((int)pC - (int)pA)); printf("sizeof(MyStruct) : %d \r\n", (int)sizeof(MyStruct)); }
結果は下記のようになった。
Address A : 7620e710 Address B : 7620e714 Address C : 7620e718 Address B-A : 4 Address C-A : 8 sizeof(MyStruct) : 12
次に、構造体を下記のように、2つ目のメンバのlongをlong longに変更すると、
struct MyStruct { char A; // char=1バイト long long B; // long long =8バイト char C; // char=1バイト } ;
下記のようになった。
Address A : 49a4e710 Address B : 49a4e718 Address C : 49a4e720 Address B-A : 8 Address C-A : 16 sizeof(MyStruct) : 24
上記2パターンを、x64、x86でビルドして実行してみたのだが、結果はどちらも同じ結果になった。(バイト数は変わらない)
下記のMSDocsによると、その構造体の中の一番大きなメンバに合わせて、アライメントが決まるらしい。
https://learn.microsoft.com/ja-jp/cpp/build/x64-software-conventions?view=msvc-170
なので、最大がlongのときは4で、最大がlong longのときは8であるっぽい。
C#でやってみる
書いたコード
ufcppの複合型のレイアウトを参考に、下記を書いてみた。
using System.Runtime.InteropServices; namespace StructLayoutJikken { [StructLayout(LayoutKind.Auto, Pack = 8)] struct Sample { public byte A; public long B; public byte C; } class Program { static unsafe void Main() { var a = default(Sample); var p = &a; var pa = &a.A; var pb = &a.B; var pc = &a.C; Console.WriteLine($"Address A = 0x{((int)p).ToString("x")}"); Console.WriteLine($"Address pA = 0x{((int)pa).ToString("x")}"); Console.WriteLine($"Address pB = 0x{((int)pb).ToString("x")}"); Console.WriteLine($"Address pC = 0x{((int)pc).ToString("x")}"); Console.WriteLine($@"サイズ: {sizeof(Sample)} A: {(long)pa - (long)p} B: {(long)pb - (long)p} C: {(long)pc - (long)p} "); } } }
StructLayoutのパラメータには、下記のようなものがある。
LayoutKind
値 | 意味 |
---|---|
Auto | 自動。ええようにしてくれる。マネージドのみになるらしい。 |
Explicit | Packで指定したアライメントにする。 各メンバーの位置を [FieldOffset] を書いて指定できる。 |
Sequential | 定義した順番にメンバーを前から配置する。アライメントはPackで指定できる。 構造体の場合はこれが既定値。 |
Pack
規定値は0。(現在のプラットフォームの既定値を使う)
値は、0、1、2、4、8、16、32、64、128 でないといけない。
実験1
上のC#コードをそのまま動かすと、下記のようになった。
配置が自動調整されて、Bが先頭に来てる。またAとCが
Address A = 0x25b7e510 Address pA = 0x25b7e518 Address pB = 0x25b7e510 Address pC = 0x25b7e519 サイズ: 16 A: 8 B: 0 C: 9
実験2
Packの値を消した。
[StructLayout(LayoutKind.Auto)] struct Sample { public byte A; public long B; public byte C; }
結果、変わらず。
Address A = 0x2237eac0 Address pA = 0x2237eac8 Address pB = 0x2237eac0 Address pC = 0x2237eac9 サイズ: 16 A: 8 B: 0 C: 9
実験3
Sequentialにした。
[StructLayout(LayoutKind.Sequential)] struct Sample { public byte A; public long B; public byte C; }
A→B→Cの順番に並んだ。
Address A = 0xe0ffe758 Address pA = 0xe0ffe758 Address pB = 0xe0ffe760 Address pC = 0xe0ffe768 サイズ: 24 A: 0 B: 8 C: 16
実験4
Explicitにして、FieldOffsetの値を0,16,32にした。
[StructLayout(LayoutKind.Explicit)] struct Sample { [FieldOffset(0)] public byte A; [FieldOffset(16)] public long B; [FieldOffset(32)] public byte C; }
FieldOffsetで指定した位置に、各メンバーが並んだ。
Address A = 0x11b7e888 Address pA = 0x11b7e888 Address pB = 0x11b7e898 Address pC = 0x11b7e8a8 サイズ: 40 A: 0 B: 16 C: 32
やってみた感想
C#のほうだと、Pack
の値を調節することで、構造体のメンバの位置を調節できた。
→COMを使うにあたり、PropVariantの共用体(union)をC#に直すときに、
LayoutKind.Explicit
にして、[FieldOffset(x)]
の位置を重ねる
ということをするために使える。
逆に、C++で、Packに相当する設定が今回見つけられなかった。 C++でアラインメントを調製する方法はあるのかな?
参考
複合型のレイアウト
https://ufcpp.net/study/csharp/interop/memorylayout/
StructLayoutAttribute クラス