![]() | Little Tips for Japanese Developers with Microsoft Visual Basic Version 4.0, 32bit, for Windows |
Vbt9001 ユーザー定義型のDoublewordアライメントを検証する
The窓の料理人
有限会社ガリバー 長谷川 勝規
Visual BasicからDLLを呼び出す際には、ユーザー定義型の構造体(以下、構造体)を引数として引き渡すことがあります。Visual Basic Version 4.0, 32bit(以下、Visual Basic 4.0, 32bit)では、構造体のアライメントに関して、思わぬ落とし穴が潜んでいますので、注意しておかなければなりません。では、この思わぬ落とし穴とは、いったい何なのでしょうか? それは、ずばりメモリブロックの穴、すなわちバイト境界の問題です。以下に、簡単なサンプル・プログラムを例にして、このバイト境界の問題を検証してみます。
構造体のアライメントは、従来のVisual Basic, 16bitではByteアライメントが採用されていましたが、Visual Basic 4.0, 32bitではDoublewordアライメントが採用されています。すなわち、構造体のメンバのパッキングは、1バイトで位置合わせする仕様から、4バイトで位置合わせする仕様に変更されいます。この変更により、Visual Basic 4.0, 32bitから呼び出して使うためのDLLをビルドする際には、コンパイラのオプションにある構造体メンバのアライメントを、4バイトに設定しておくことが賢明である、ということになっているようです。Visual Basic 4.0, 32bitで定義した構造体と、DLLが引数として期待する構造体が、同じ構造のメモリブロックになるように考慮しておかないと、期待する動作を得られない可能性があるのです。

では、構造体のDoublewordアライメントを検証するサンプル・プログラムを実際に作成して、確かめてみることにしましょう。まず、構造体のDoublewordアライメントの対象として、構造体のメンバのパッキングが、4バイトで位置合わせされるような構造体を定義します。この構造体の先頭のメンバPartOfIntegerは、Visual Basic 4.0, 32bitの整数型であり、2バイトです。2番目のメンバPartOfLongは、長整数型であり、4バイトです。したがって、この構造体のサイズをLen関数で取得すると、6バイトになります。
' 整数型、長整数型のメンバを定義する構造体
Type INTEGER_LONG
PartOfInteger As Integer
PartOfLong As Long
End Type
' ある位置から別の位置にメモリブロックを移動する関数の宣言
Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" _
(Destination As Any, _
Source As Any, _
ByVal Length As Long)
Dim udtSourceStruct As INTEGER_LONG
Dim udtDestinationStruct As INTEGER_LONG
Dim lngSourceStructLength As Long
Dim lngDestinationStructLength As Long
' ラベルを初期化
Label3.Caption = ""
Label4.Caption = ""
' 移動元の構造体を設定
With udtSourceStruct
.PartOfInteger = &H100
.PartOfLong = &H5040302
End With
lngSourceStructLength = Len(udtSourceStruct)
' 移動先の構造体を初期化
With udtDestinationStruct
.PartOfInteger = &H0
.PartOfLong = &H0&
End With
lngDestinationStructLength = Len(udtDestinationStruct)
' 構造体のメモリブロックを移動
MoveMemory udtDestinationStruct, _
udtSourceStruct, _
lngDestinationStructLength
' 移動元の構造体メンバを表示
With udtSourceStruct
Label3.Caption = lngSourceStructLength & ", " & _
Hex(.PartOfInteger) & ", " & _
Hex(.PartOfLong)
End With
' 移動先の構造体メンバを表示
With udtDestinationStruct
Label4.Caption = lngDestinationStructLength & ", " & _
Hex(.PartOfInteger) & ", " & _
Hex(.PartOfLong)
End With
このサンプル・プログラムを実行すると、この構造体の2番目のメンバPartOfLongの上位wordが欠落します。これは、期待通りの結果です。それは、この構造体が、実際のメモリ上では、4バイトで位置合わせするように調整されてるからです。

このサンプル・プログラムでは、構造体のメモリブロックを移動するためにWin32API関数を使っているのですが、その関数の最後の引数は、移動するメモリブロックのサイズを指定します。このサイズには、Visual Basic 4.0, 32bitのLen関数で得た構造体変数udtDestinationStructのサイズ、すなわち構造体INTEGER_LONGのサイズ(6バイト)を指定しています。

ところが、Visual Basic 4.0, 32bitでは、Doublewordアライメントが採用されていますので、実際のメモリブロックでは、この構造体の先頭のメンバPartOfIntegerと、2番目のメンバPartOfLongとの間に、2バイト分の穴が空きます。このように確保されたメモリブロックを先頭から6バイト移動しても、最後の2バイト(2番目のメンバ PartOfLongの上位word)が移動されないのは、当然のことです。したがって、移動先の構造体は、2番目のメンバPartOfLongの上位wordが欠落して、0x00000302となるのです。
これは問題だと思われるかも知れませんが、安心してください。ここでは、アライメントの問題を検証するために、敢えてLen関数を使って構造体のサイズを取得しています。実は、この問題を解決する関数は、LenB関数として、Visual Basic 4.0, 32bitで提供されているのです。
ちなみに、構造体のサイズを取得する場合のLen関数とLenB関数の違いは、Visual Basic 4.0, 32bitのヘルプにあるLen関数のトピックの下段に、[注意]として記載されています。
' 移動元の構造体を設定
With udtSourceStruct
.PartOfInteger = &H100
.PartOfLong = &H5040302
End With
lngSourceStructLength = LenB(udtSourceStruct)
' 移動先の構造体を初期化
With udtDestinationStruct
.PartOfInteger = &H0
.PartOfLong = &H0&
End With
lngDestinationStructLength = LenB(udtDestinationStruct)
' 構造体のメモリブロックを移動
MoveMemory udtDestinationStruct, _
udtSourceStruct, _
lngDestinationStructLength

このような構造体のアライメントの問題は、VB32.EXEがSetupされたフォルダにインストールされるVB4DLL.TXTというドキュメントファイルに、詳しく説明されています。ソフトウェアと一緒にインストールされるドキュメントファイルには、得てして重要な情報が記載されているものです。何気なくフォルダを眺めていて、これは何が書いてあるんだ?、何のドキュメントファイルなんだ?、と思ったならば、即時に目を通しておくべきです。その場ですべてを読み下して、理解しなくても構いません。何が書いてあるか、という要点を、把握しておくだけで充分です。そんな些細な積み重ねが、実際の開発に役立つことは、大いにありえるからです。
サンプル・プログラム(VBT9001.LZH)はダウンロードできます。
Little Tipsへのご意見、ご要望をお待ちしております。
E-MAIL:mado@galliver.co.jp
