構造体が拡張されたときの動作
- 以下,UNICODE ビルドで試した SystemParametersInfo/SPI_GETNONCLIENTMETRICS のパラメータと,Windows XP SP2 及び Windows Vista SP1 での API 成否の関係.uiParam が無視されていて,NONCLIENTMETRICS::cbSize のみで互換性判定を行っているらしいことが分かる.
- だからといってドキュメントを無視すべきではない.
例えば構造体にひとつ要素を追加すると,無関係な API でバグ疑惑が発生するという話 - NyaRuRuの日記
uiParam cbSize XP SP2 (x86) Vista SP1 (x86) 0 500 success success 500 500 success success 504 500 success success 0 504 fail success 500 504 fail success 504 504 fail success 504 0 fail fail 500 0 fail fail
なるほど。疑問に思ったら試してみることが大切ですね。
構造体が拡張された前例としてWindows 95→98でのMENUITEMINFOがあります。SetMenuItemInfoでテストしてみました。
cbSize | 95 OSR2 | 98 SE | NT4.0 SP6 | 2000 SP4 | XP SP2 | Vista | 備考 |
---|---|---|---|---|---|---|---|
40 | x | x | o | x | x | x | 不正な値 |
44 | o | o | o | o | o | o | 旧仕様 |
48 | x | o | o | o | o | o | _WIN32_WINNT >= 0x0500 |
NT4.0のSetMenuItemInfoはサイズをチェックしていないようです。ゼロを含め、任意の値で呼び出しに成功しました。(95もOSR2以前のバージョンではチェックしていないかもしれません)
SystemParametersInfo(SPI_GETNONCLIENTMETRICS)でもやってみました。
cbSize | 95 OSR2 | 98 SE | NT4.0 SP6 | 2000 SP4 | XP SP2 | Vista | 備考 |
---|---|---|---|---|---|---|---|
336 | x | x | x | x | x | x | 不正な値 |
340 | o | o | o | o | o | o | 旧仕様 |
344 | x | x | x | x | x | o | _WIN32_WINNT >= 0x0600 |
全てのOSで、サポートしているサイズ以外では失敗しています。なのでNT4.0のSetMenuItemInfoは例外と捉えるべきですね。
ところでバージョン毎に構造体のサイズが異なるものの代表例といえばShell APIコモンコントロールです。ヘッダを見てみると、PlatformSDK以降では構造体のサイズを取得するCCSIZEOF_STRUCTというマクロがあり、
#define TTTOOLINFOA_V1_SIZE CCSIZEOF_STRUCT(TTTOOLINFOA, lpszText) #define TTTOOLINFOA_V2_SIZE CCSIZEOF_STRUCT(TTTOOLINFOA, lParam) #define TTTOOLINFOA_V3_SIZE CCSIZEOF_STRUCT(TTTOOLINFOA, lpReserved)
というように複数のバージョンを定義しています。
そんなわけで下記の件ですが、
さてこの対処方法ですが、理想的には、まだユーザ数の少ないSDKにパッチをあてて、NONCLIENTMETRICSを従来どおり32ビットで数えて500バイトのままにしておき、Vista専用のNONCLIENTMETRICS_AEROか何かの別の504バイトの構造体を設けることが、最良の策だと思います。どうでしょうMicrosoftさん(^^。どうせSystemParametersInfoの第三引数はPVOID型なんだし、何のポインタを入れても、cbSizeが正しければOKみたいだし。「Vistaに特化して増えた画面設定の値を使いたいけど、古いSDKになじんでいて新しい構造体の名前を知らない」、なんて人はほとんどいないと言ってよくて、むしろ、多くのアプリケーションでは、XP以下との互換を考慮して、増えたフィールドを使うことはないでしょうし。
とはいえ、実際には、#if WINVER >= 0x0600 のときだけ、NONCLIENTMETRICSを-sizeof(int)して使うしかないです。別のプログラムで発生する同じような問題で困っている人がググったとき、このエントリが少しでも役に立てば幸いです。
wxWidgetsのパッチにフォーカス - the technote
NyaRuRuさんがお書きになった通り、「-sizeof(int)」という値はいまいちですね。
MS流に書くのであればこんな感じでしょうか。
#define NONCLIENTMETRICSA_V1_SIZE \ CCSIZEOF_STRUCT(NONCLIENTMETRICSA, lfMessageFont) #define NONCLIENTMETRICSW_V1_SIZE \ CCSIZEOF_STRUCT(NONCLIENTMETRICSW, lfMessageFont) #define NONCLIENTMETRICSA_V6_SIZE \ CCSIZEOF_STRUCT(NONCLIENTMETRICSA, iPaddedBorderWidth) #define NONCLIENTMETRICSW_V6_SIZE \ CCSIZEOF_STRUCT(NONCLIENTMETRICSW, iPaddedBorderWidth) ... #if defined(__WXMSW__) && defined(__WIN32__) && defined(SM_CXMENUCHECK) NONCLIENTMETRICS nm; nm.cbSize = sizeof(NONCLIENTMETRICS); if ( !::SystemParametersInfo(SPI_GETNONCLIENTMETRICS,0,&nm,0) ) { #if WINVER >= 0x0600 // fallback to legacy size #ifdef UNICODE nm.cbSize = NONCLIENTMETRICSW_V1_SIZE; #else nm.cbSize = NONCLIENTMETRICSA_V1_SIZE; #endif // UNICODE if ( !::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &nm, 0) ) #endif // WINVER >= 0x0600 { // maybe we should initialize the struct with some defaults? wxLogLastError(_T("SystemParametersInfo(SPI_GETNONCLIENTMETRICS)")); } }
結論: MicrosoftがCCSIZEOF_STRUCTをShell APIコモンコントロール以外にも適用すれば解決。
例えば構造体にひとつ要素を追加すると,無関係な API でバグ疑惑が発生するという話 - NyaRuRuの日記
- それはそれとして,Visual C++ 2008 (含む Express Edition) で WINVER を設定しないと Windows Vista 以降専用になるのは酷いかも.
手元の環境で試したら以下のような結果でした。
バージョン | WINVER | _WIN32_WINDOWS | _WIN32_WINNT |
---|---|---|---|
VC++ 6.0 | 0x400 | undefined | undefined |
VC++ 6.0 + Platform SDK | 0x501 | 0x500 | undefined |
VC++ 6.0 + Windows SDK | 0x600 | undefined | 0x600 |
VC++ 2003 | 0x501 | undefined | undefined |
VC++ 2005 | 0x501 | undefined | undefined |
VC++ 2008 | 0x600 | undefined | 0x600 |
MS的には、未定義ならその時の最新の値を定義するのはまあ仕方ないという気はします。
VC++2003の時点で標準環境としてXPが想定されているわけで、これも旧環境では動かないものが生成されるわけです。
結論としては「Windowsアプリケーションの開発者はWINVERか_WIN32_WINNTを必ず定義せよ」ということになるかと思います。