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

そこで、この構造体の型を持つ変数を2つ宣言して、一方の変数のメモリブロックをもう一方へ移動してみましょう。メモリブロックを移動するWin32API関数は、RtlMoveMemory()であり、kernel32.dllでExportsされています。

    ' ある位置から別の位置にメモリブロックを移動する関数の宣言
    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


Visual Basic 4.0, 32bitでは、構造体のサイズをLenB関数で取得した場合は、実際のメモリブロックのサイズを返します。この構造体のサイズは、Doublewordアライメントにより位置合わせされたメモリブロックのサイズになります。その値を移動するメモリブロックのサイズに指定すれば、この構造体の2番目のメンバPartOfLongの上位wordは欠落しません。




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


サンプル・プログラム(VBT9001.LZH)はダウンロードできます。

Little Tipsへのご意見、ご要望をお待ちしております。
E-MAIL:mado@galliver.co.jp


このホームページに記載されている会社名ならびに製品名は、各社の登録商標 または商標です。



有限会社ガリバー

〒483 愛知県江南市村久野町平野119番地
Tel:0587-52-3030 Fax:0587-52-3031