Windows では、構造体の先頭のメンバで*1、その構造体のサイズを表すようになっているものが多いです。
API を呼び出す側で構造体のサイズを設定することにより、将来 Windows の機能追加で構造体にメンバが追加されても、古いプログラムの互換性が保てるようになっています。
例えば LVGROUP という構造体は、以下のように定義されています。
typedef struct tagLVGROUP { UINT cbSize; UINT mask; LPWSTR pszHeader; int cchHeader; LPWSTR pszFooter; int cchFooter; int iGroupId; UINT stateMask; UINT state; UINT uAlign; #if _WIN32_WINNT >= 0x0600 LPWSTR pszSubtitle; UINT cchSubtitle; LPWSTR pszTask; UINT cchTask; LPWSTR pszDescriptionTop; UINT cchDescriptionTop; LPWSTR pszDescriptionBottom; UINT cchDescriptionBottom; int iTitleImage; int iExtendedImage; int iFirstItem; UINT cItems; LPWSTR pszSubsetTitle; UINT cchSubsetTitle; #endif } LVGROUP, *PLVGROUP;
_WIN32_WINNT >= 0x0600 ということは、pszSubtitle 以降のメンバは Windows Vista で追加されたということになります。
これを以下のように利用すると、
LVGROUP lvg; lvg.cbSize = sizeof(LVGROUP);
_WIN32_WINNT が 0x0600 以上の場合、Vista より前の XP などでは動作しなくなります。
そういう時に困らないようにということで、過去のバージョンの構造体のサイズを表す定数が定義されています。
LVGROUP の場合、LVGROUP_V5_SIZE として Vista で追加されたメンバを除いたサイズが、以下のように定義されています。
#define LVGROUP_V5_SIZE CCSIZEOF_STRUCT(LVGROUP, uAlign)
CCSIZEOF_STRUCT というのは、構造体のあるメンバまでのサイズを求めるマクロで、意味としては以下のような計算を行うようになっています。
offsetof(struct, member) + sizeof(struct.member)
しかし、実はこれでは問題があるのです。それは、構造体のパディングが考慮されないという点です。
ある構造体 A があったとして、sizeof(A) は A のメンバのサイズの合計になるでしょうか。
答えは「なる場合もあれば、ならない場合もある」です。
なぜなら、構造体のメンバ間や末尾には、CPU の都合のいいようにパディングと呼ばれる余分な領域が追加されることがあるからです。
これにより、x64 ビルドで _WIN32_WINNT の値を 0x0600 未満に定義した場合(つまり Vista 以降のメンバを除外した場合)、sizeof(LVGROUP) は 56 ですが、
_WIN32_WINNT を 0x0600 以上に定義した場合の LVGROUP_V5_SIZE は 52 となり、サイズが食い違ってしまいます。
これは Windows のヘッダのバグでしょう。
私はこのバグにはまってしまい、x86 ビルドだと動くが x64 ビルドだとなぜか動かないという問題が出て、原因の解明に時間を掛けてしまいました。
構造体とサイズを表すメンバに関係して、微妙な問題が他にもあるので、次に続きます。
0 件のコメント:
コメントを投稿