構造体 構造体を取り扱うには System.Runtime.InteropServices 名前空間をインポートして置くと便利で有る Imports System.Runtime.InteropServices using System.Runtime.InteropServices; C# ユーザー定義型 (Type) と構造体 (Structure) 6.0 のユーザー定義型 (Type) を.NET 以降の構造体 (Structure) に移行する時に問題に成るのが 配列と固定長文字列の要素で有る Private Type MyType ItemArray(5) As Integer ItemString As String * 100 End Type 6.0 上記のユーザー定義型をアップグレードすると 下記の様な構造体に成る Private Structure MyType Dim ItemArray() As Short <VBFixedString(100), MarshalAs(UnmanagedType.ByValTStr, SizeConst:=100)> Dim ItemString As String Public Sub Initialize() ReDim ItemArray(5) End Sub End Structure 此の構造体には 配列の要素数を初期化する為のユーザー定義関数 (Initialize) が含まれて居り 此の関数を利用して配列の要素を初期化するように TODO コメントも追加されるので 此のコメントを参考に 配列の要素数を初期化する 亦 固定長文字列に付いては 可変長文字列に変更されるが 固定配列やマーシャリングの為の属性が追加されて居るので 固定長文字列の様に利用する事が出来る の文字列は 既定では可変長で有る 固定長文字列を必要とする のファイル入出力関数 (FileGet や FilePut 等 ) を使う時には VBFixedStringAttribute 属性を使用する 此の属性は 此等の関数への情報提供の為の属性で 可変長の文字列を固定長の文字列に変換する為には使用出来ない 従って 此の属性に依って文字列自身の実際の長さが変更される事は無い点に注意を要する -1-
System.Runtime.InteropServices.StructLayout 属性 System.Runtime.InteropServices.StructLayout 属性を使用すると 構造体のメモリ内での配置をカスタマイズする事が出来る 例えば StructLayout(LayoutKind.Explicit) 属性と FieldOffset 属性を使用すると C/C++ の共用体と呼ばれる物を作成する事が出来る 下記のコードセグメントでは 構造体 Test1 の総ての要素 ( フィールド ) がメモリ内の同じ場所で開始される <StructLayout(LayoutKind.Explicit)> _ Private Structure Test1 <FieldOffset(0)> Dim I As Integer <FieldOffset(0)> Dim D As Double <FieldOffset(0)> Dim C As Char <FieldOffset(0)> Dim B As Byte End Structure [StructLayout(LayoutKind.Explicit)] private struct Test1 public int i; public double d; public char c; public byte b; } C# 0 1 2 3 4 5 6 7 int i double d char c byte b 要素 ( フィールド ) が別の明示的に設定された場所から開始される別の例を下記に示す <StructLayout(LayoutKind.Explicit)> _ Private Structure Test2 <FieldOffset(0)> Dim L As Long <FieldOffset(0)> Dim I1 As Integer <FieldOffset(4)> Dim I2 As Integer <FieldOffset(8)> Dim D As Double <FieldOffset(12)> Dim C As Char <FieldOffset(14)> Dim B As Byte End Structure -2-
[StructLayout(LayoutKind.Explicit)] private struct Test2 public long l; public int i1; [FieldOffset(4)] public int i2; [FieldOffset(8)] public double d; [FieldOffset(12)] public char c; [FieldOffset(14)] public byte b; } C# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 long l double d int i1 int i2 char c byte b 2 個の int 要素の i1 と i2 は long 要素の l と同じメモリ位置を共有する 此の様な構造体レイアウトの制御は プラットフォーム呼出の時に便利で有る LayoutKind 列挙型 Auto Explicit Sequential ランタイムは アンマネージメモリ内のオブジェクトのメンバに対して適切なレイアウトを自動的に選択する 此の列挙体メンバで定義されたオブジェクトは マネージコードの外に公開出来ない 公開しようとすると 例外が生成される アンマネージメモリ内に有るオブジェクトの各メンバの正確な位置は 明示的に制御される 各メンバは FieldOffsetAttribute 属性を使用して 其の型内のフィールドの位置を指定する必要が有る オブジェクトのメンバは アンマネージメモリにエクスポートする時に表示される順番に従ってレイアウトされる メンバは StructLayoutAttribute.Pack で指定したパッキングに従ってレイアウトされる メンバは非連続する事が出来る <StructLayout(LayoutKind.Sequential)> _ Public Structure Point Public x As Integer Public y As Integer End Structure 'Point <StructLayout(LayoutKind.Explicit)> _ Public Structure Rect <FieldOffset(0)> Public left As Integer -3-
<FieldOffset(4)> Public top As Integer <FieldOffset(8)> Public right As Integer <FieldOffset(12)> Public bottom As Integer End Structure 'Rect [StructLayout(LayoutKind.Sequential)] public struct Point public int x; public int y; } [StructLayout(LayoutKind.Explicit)] public struct Rect public int left; [FieldOffset(4)] public int top; [FieldOffset(8)] public int right; [FieldOffset(12)] public int bottom; } [StructLayout(LayoutKind::Sequential)] value struct Point public: int x; int y; }; C# C++ [StructLayout(LayoutKind::Explicit)] value struct Rect public: int left; [FieldOffset(4)] int top; [FieldOffset(8)] int right; [FieldOffset(12)] int bottom; }; -4-
MarshalAsAttribute クラス Marshal とは プログラミング用語では 或るプログラムで利用されるデータ形式を 異なるプログラムで利用可能な形式に変換する事を謂い 此のクラスは マネージコードとアンマネージコードとの間のデータのマーシャリング方法を示す メンバー名 AnsiBStr AsAny Bool BStr ByValArray ByValTStr Currency CustomMar shaler Error FunctionPtr HString I1 I2 I4 I8 説明長さを示すプレフィックスを付けた &1; バイトの ANSI 文字列 此のメンバーは String データ型で使用出来る 実行時にオブジェクトの型を確認し 其の型としてオブジェクトをマーシャリングする動的な型 此のメンバーは プラットフォーム呼出メソッドに而巳有効で有る 4 バイトの Boolean 値 (true!= 0 false = 0) 此れは Win32 BOOL 型で有る 長さを示すプリフィックスを付けた &2; バイトのUnicode 文字列 此のメンバー (COM の既定の文字列 ) は String データ型で使用出来る MarshalAsAttribute.Value プロパティを ByValArray に設定した場合 SizeConst フィールドは 配列の要素数を示す様に設定する必要が有る ArraySubType フィールドには 文字列型を区別する必要が有る場合に オプションとして配列要素の UnmanagedType を格納出来る 此の UnmanagedType は 要素が構造体にフィールドとして定義されて居る配列で而巳使用出来る 構造体に定義されて居るインライン固定長文字配列で使用する ByValTStr で使用する文字型は 格納される構造体に適用する System.Runtime.InteropServices. StructLayoutAttribute 属性の引数 System.Runtime.InteropServices.CharSet に依って決定される 配列のサイズを示す場合は 常に MarshalAsAttribute.SizeConst フィールドを使用する 通貨型 10 進値を Decimal ではなく COM 通貨型としてマーシャリングする為に System.Decimal で使用する MarshalAsAttribute.MarshalType 又は MarshalAsAttribute.MarshalTypeRef フィールドと共に使用する場合に カスタムマーシャラークラスを指定する MarshalAsAttribute.MarshalCookie フィールドは カスタムマーシャラーに追加の情報を渡す為に使用出来る 此のメンバーは任意の参照型で使用出来る I4 又は U4 に関連付けられたネイティブな型 此の型に依り パラメーターはエクスポート先のタイプライブラリに HRESULT としてエクスポートされる C スタイルの関数ポインターとして使用出来る整数値 此のメンバーは Delegate データ型又は Delegate から継承した型で使用出来る Windows ランタイム文字列 此のメンバーは System.String データ型で使用出来る 1 バイト符号付き整数 此のメンバーを使用すると Boolean 値を 1 バイトの C スタイル bool(true = 1 false = 0) に変換出来る 2 バイト符号付き整数 4 バイト符号付き整数 8 バイト符号付き整数 IDispatch COM の IDispatch ポインター (Microsoft 6.0 では Object) IInspectable Windows ランタイムインターフェイスポインター 此のメンバーは Object データ型で使用出来る Interface COM インターフェイスポインター インターフェイスの Guid は クラスメタデータから取得する インターフェイス型を直接指定する場合 又は クラスに適用する場合は既定のインターフェイス型を指定する場合に 此のメンバーを使用する 此のメンバーは Object データ型に適用すると UnmanagedType.IUnknown と同じ動作を -5-
生成する IUnknown COM IUnknown ポインター 此のメンバーは Object データ型で使用出来る LPArray C スタイル配列の最初の要素へのポインター マネージコードからアンマネージコードにマーシャリングする場合 配列長はマネージ配列長に依って決定される アンマネージコードからマネージコードにマーシャリングする場合 配列の長さは MarshalAsAttribute.SizeConst フィールドと MarshalAsAttribute.SizeParamIndex フィールドに依って決まる 文字列の型を区別する必要が有る場合は 配列内の要素のアンマネージ型も考慮される LPStr 終端が null の &1; バイトの ANSI 文字列 此のメンバーは System.String データ型及び System.Text.StringBuilder データ型で使用出来る LPStruct マネージ書式指定クラスをマーシャリングする時に使用する C スタイル構造体へのポインター 此のメンバーは プラットフォーム呼出メソッドに而巳有効で有る LPTStr プラットフォームに依存する文字列 Windows 98 では ANSI Windows NT と Windows XP では Unicode 型 LPTStr の文字列のエクスポートがサポートされて居ない為 此の値は COM 相互運用ではサポートされず プラットフォーム呼出而巳でサポートされる LPUTF8Str UTF-8 でエンコードされた文字列へのポインター LPWStr 終端が null の 2 バイトの Unicode 文字列 R4 4 バイト浮動小数点数 R8 8 バイトの浮動小数点数 SafeArray SafeArray は 関連付けられた配列データの型 ランク 及び 境界を格納する自己記述型の配列で有る MarshalAsAttribute.SafeArraySubType フィールドと併せて此のメンバーを使用する事に依って 既定の要素の型をオーバーライド出来る Struct マネージ書式指定クラスと値型をマーシャリングする為に使用する VARIANT SysInt プラットフォーム依存 符号付き整数 :32 ビット Windows では 4 バイト 64 ビット Windows では 8 バイト SysUInt プラットフォーム依存 符号無し整数 :32 ビット Windows では 4 バイト 64 ビット Windows では 8 バイト TBStr 長さを示すプレフィックスを付けた プラットフォームに依存する char 文字列 Windows 98 では ANSI Windows NT では Unicode 此の BSTR に似たメンバーを使用する事は殆ど無い U1 1 バイト符号無し整数 U2 2 バイト符号無し整数 U4 4 バイト符号無し整数 U8 8 バイト符号無し整数 VariantBool 2 バイトの OLE 定義 VARIANT_BOOL 型 (true = -1 false = 0) VBByRefStr で アンマネージコードの文字列を変更し 結果をマネージコードに反映出来る様にする値 此の値は プラットフォーム呼び出しで而巳サポートされる Declare 文の引数型を変換する MarshalAs 属性.NET 以降では.NET Framework の幅広いクラスライブラリにアクセス出来る為 Visual Basic 6.0 と異なり Win32 API を呼び出す Declare 文を使用する機会は減少したのではないかと思う しかし Win32 API を呼び出す機会がゼロに成った訳ではなく 依然として Declare 文は存在して居る 此の時 問題に成るのが 文字列等のデータ型が.NET 以降と Win32 API で互換性が全く無い事で有る.NET 以降の文字列は System.String クラスの文字列クラスに依り表現さ -6-
れるが Win32 API では文字型の配列として表現される 此の相違を乗り越えるには API 呼び出し時に適切なデータ型の変換が必要と成る 此の時の変換方法を指定する手段として MarshalAs 属性が使用される 以下は 実際に MarshalAs 属性を使用したサンプルプログラムで有る 1 Imports System.Runtime.InteropServices 2 3 Public Class Form1 4 Inherits System.Windows.Forms.Form 5 6 Windows フォームデザイナで生成されたコード 7 8 Declare Auto Sub MessageBox Lib "user32.dll" ( _ 9 ByVal hwnd As Integer, _ 10 <MarshalAs(UnmanagedType.LPTStr)> ByVal lptext As String, _ 11 <MarshalAs(UnmanagedType.LPTStr)> ByVal lpcaption As String, _ 12 ByVal utype As Integer) 13 14 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles MyBase.Load 15 MessageBox(0, "Hello!", "Sample Program", 0) 16 End Sub 17 End Class 此処で注目す可き点は 8~12 行目の Declare 文の中で使用されて居る MarshalAs 属性で有る 10 行目と 11 行目の文字列は 其の儘 Win32 API に渡す事が出来ないので 変換する必要が有る 其処で MarshalAs 属性は其の必要性を指定して居る 具体的に何んなデータ型に変換するかは UnmanagedType 列挙型の値で指定して居る MarshalAs 属性と UnmanagedType 列挙型は System.Runtime.InteropServices 名前空間に属して居るので 1 行目の Imports 文で此の名前空間を指定して居る 扨て Win32 API 呼び出し時の文字列に関する今一つの問題は Win32 API には文字コードの相違に依って同じ機能を持った API が実際には 2 つ存在して居る場合が有る事だ 詰り 其の 2 つの孰れを呼び出すかの明示的な指定と 孰れの文字コードを指定して引数を変換させるかと謂う指定が必要とされる 上記のサンプルプログラムの場合 Declare キーワードの次の Auto キーワードが OS の種類に応じて 自動的に 2 つの Win32 API の中から選択する事を指定して居る Windows 9X 系では ANSI 系を Windows NT 系では Unicode 系の API が呼ばれる事に成ると思う 引数の変換に指定した UnmanagedType.LPTStr と謂う値は 状況に応じて適切な文字コードを選択すると謂う機能を持つ 併し 自動判定に頼らず 明示的に指定する事も出来る 以下は 明示的に Unicode 系の Win32 API を選ぶ様に記述したソースで有る 1 Imports System.Runtime.InteropServices 2 3 Public Class Form1 4 Inherits System.Windows.Forms.Form 5 6 Windows フォームデザイナで生成されたコード 7-7-
8 Declare Auto Sub MessageBoxW Lib "user32.dll" ( _ 9 ByVal hwnd As Integer, _ 10 <MarshalAs(UnmanagedType.LPWStr)> ByVal lptext As String, _ 11 <MarshalAs(UnmanagedType.LPWStr)> ByVal lpcaption As String, _ 12 ByVal utype As Integer) 13 14 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles MyBase.Load 15 MessageBoxW(0, "Hello!", "Sample Program", 0) 16 End Sub 17 End Class ソースコードの変更点は 2 つ有る 1 つは 8 行目のメソッド名を MessageBox から MessageBoxW に変更し Unicode 系の MessageBox API の本当の名前を指定した事だ 今 1 つは 10~11 行目の MarshalAs 属性で Unicode 文字列を意味する UnmanagedType.LPWStr と謂う値を指定した事で有る 猶 謂う迄も無く メソッドの名前は Declare 文の Alias キーワードを活用すれば変更可能なので MessageBox と謂う名前で呼び出す様にも出来る 逆に ANSI 系 ( 日本ではシフト JIS) を用いて文字列を受け渡す様に変更したサンプルプログラムを下記に示す 1 Imports System.Runtime.InteropServices 2 3 Public Class Form1 4 Inherits System.Windows.Forms.Form 5 6 Windows フォームデザイナで生成されたコード 7 8 Declare Auto Sub MessageBoxA Lib "user32.dll" ( _ 9 ByVal hwnd As Integer, _ 10 <MarshalAs(UnmanagedType.LPStr)> ByVal lptext As String, _ 11 <MarshalAs(UnmanagedType.LPStr)> ByVal lpcaption As String, _ 12 ByVal utype As Integer) 13 14 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles MyBase.Load 15 MessageBoxA(0, "Hello!", "Sample Program", 0) 16 End Sub 17 End Class 此処では Win32 API の名前は ANSI 系の Win32 API としての本名で有る MessageBoxA に変更し 変換するデータ型を UnmanagedType.LPStr として居る UnmanagedType.LPStr は ANSI 系の文字列を示す値で有る -8-