りすと亭ソースの32bit化の作業がさっぱり進行しません。
また、64bit移植性関連の問題で無駄な時間を費やしてしまいました。しかも、今回のは致命的です。
以下はVisual Studio.NET 2003を使用することを前提に説明します。
注: この問題そのものは、少なくとも2001年の時点でMicrosoftが認識済みの問題の再発見に過ぎません(末尾の参考リンク参照)。このコンテンツの内容は、問題の詳細をメモっておく備忘録的な役割を持って書かれるものです。
概要 §
RegisterClassEx APIで指定するエクストラなウィンドウ領域(クラス領域ではない)にポインター値を書き込む時、SetWindowLongPtr APIを使うとされています。このAPIは、MSDNのSetWindowLongPtr Functionによれば、第3引数がLONG_PTR型であるとされています。
しかし、この記述は32bitシステムにおいては真っ赤な嘘であり、実際には第3引数がLONGであるSetWindowLong APIにマクロ定義で置き換えられます。
32bitシステムにおいて、LONGとLONG_PTRは完全に互換性のある型であるために、このマクロ定義は問題を発生させません。しかし、__w64キーワードによる64bit移植性の検証機能を使用する場合には、この2つの型は互換であるとは見なせません。その結果、警告が発せられます。
では引数をLONG型で書けば良いのかというと、そのように記述すると今度は64bit環境で型が整合しません。64bit環境では、SetWindowLongPtr APIは確かに実体が存在し、その第3引数は間違いなくLONG_PTR型です。
問題の具体的な所在 §
WinUser.hから問題の箇所を引用します。
#ifdef _WIN64
(中略)
以下の通り、64bit環境ではAPIの実体があり、戻り値と第3引数はLONG_PTR型です。
WINUSERAPI
LONG_PTR
WINAPI
SetWindowLongPtrA(
HWND hWnd,
int nIndex,
LONG_PTR dwNewLong);
WINUSERAPI
LONG_PTR
WINAPI
SetWindowLongPtrW(
HWND hWnd,
int nIndex,
LONG_PTR dwNewLong);
#ifdef UNICODE
#define SetWindowLongPtr SetWindowLongPtrW
#else
#define SetWindowLongPtr SetWindowLongPtrA
#endif // !UNICODE
#else /* _WIN64 */
(中略)
64bitではないとき=32bitのとき、以下の通り、マクロでSetWindowLongPtrはSetWindowLongに置き換えられます。
#define SetWindowLongPtrA SetWindowLongA
#define SetWindowLongPtrW SetWindowLongW
#ifdef UNICODE
#define SetWindowLongPtr SetWindowLongPtrW
#else
#define SetWindowLongPtr SetWindowLongPtrA
#endif // !UNICODE
#endif /* _WIN64 */
SetWindowLong APIの宣言は以下のようになっています。
WINUSERAPI
LONG
WINAPI
SetWindowLongA(
IN HWND hWnd,
IN int nIndex,
IN LONG dwNewLong);
WINUSERAPI
LONG
WINAPI
SetWindowLongW(
IN HWND hWnd,
IN int nIndex,
IN LONG dwNewLong);
#ifdef UNICODE
#define SetWindowLong SetWindowLongW
#else
#define SetWindowLong SetWindowLongA
#endif // !UNICODE
つまり、戻り値と第3引数はLONG型です。
そして何が起こるのか §
__w64キーワードによる64bit移植性の検証機能を使用する場合には、LONGとLONG_PTRの混用は警告される可能性があります。LONGは32bitシステムでも64bitシステムでも32bit幅であるのに対して、LONG_PTRは32bitシステムでは32bit幅であるのに対して、64bitシステムでは64bit幅になるためです。つまり、LONG_PTRからLONGへの変換は値の一部を失う可能性があり、警告の対象になります。
従って、以下のようなコードは、__w64キーワードによる64bit移植性の検証機能が有効である32bitシステム上でコンパイルすると警告の対象となります。
コード: SetWindowLongPtr( hWnd, 0, (LONG_PTR)lpcs->lpCreateParams );
警告内容: warning C4244: '引数' : 'LONG_PTR' から 'LONG' に変換しました。データが失われているかもしれません。
しかし、ここで第3引数をLONG_PTR型にキャストせず、LONG型にキャストすると、64bitシステムで問題が発生します。SetWindowLongPtr APIは通常ポインターを扱うことを意図して使用されますが、64bitシステムのポインターは64bit幅になります。LONG_PTR型へのキャストは正しく64bit値の受け渡しを可能にしますが、32bit幅しかないLONG型へのキャストは情報の一部が欠落することを意味します。
つまり、正常に動作しない可能性があります。
対処方法 §
1) #ifdef _WIN64を用いて32bitと64bitで別のコードを書く
2) #pragmaディレクティブで、SetWindowLongPtr APIに関する部分のみ、warning C4244の警告を発生させないようにする (しかし、本来警告すべき内容まで抑止する恐れがあるので、リスクもある)
3) その他
関連情報 §
Blogs / Keith Brown SetWindowLongPtr and -Wp64
Bugslayer Optimize and Trim Your Code with New Switches in Visual C++ .NET -- MSDN Magazine, August 2001