WM_PAINT内処理異常に関する調査報告書
2006/03/25 VALGUS
2006年03月23日に見つかりました WM_PAINT 処理内の異常問題について全数チェックと対策を行いました。
その調査報告と対処方法をここにご報告いたします。
なお、今後も問題点が発見され次第、可能な限りの迅速なる告知と修正対応をいたします。
今後も変わらず VALGUS - Orange Knowledge をよろしくお願いいたします。
※ ご報告を頂きましたヴォーガ氏には大変感謝しております。ありがとうございました。
■ 本件の問題点
WM_PAINT で再描画処理を行う場合、唯一、BeginPaint();〜EndPaint();で処理しなければいけません。
BeginPaint() はデバイスコンテキストのクリッピングリージョンを自動設定したり、
WM_ERASEBKGND メッセージをウィンドウに送ったりという様々な再描画に必要な前処理を行います。
ところが私が掲載していた Programming Tips に、
当該処理を行わず GetDC() にてデバイスコンテキストを取得して処理を行った記事がありました。
このような処理を行うと WM_PAINT に必要な前処理が行われないために、
Windows のバージョンによっては致命的な症状を引き起こすことがあります。
■ 対策
まず、情報が入った時点でトップページに問題の告知と該当されると予想された範囲の注意勧告を掲載しました。
そして、本日、サイト掲載された情報の全数チェックを行いました。
全数チェックとしては、「hdc」「getdc」「delete」で全ファイルを検索して、
検索にかかった箇所とその周辺関連箇所全てを目視確認いたしました。
アーカイブファイルは全て掲載ファイルを一旦解凍の後、同様にチェックしました。
調査の結果、
問題があったのは異常が報告された [SDK-005] とダウンロードサンプルのみ
でした。
皆様には大変ご迷惑をおかけいたしましたこと、また、不安にさせてしまいましたことをここにお詫びいたします。
なお、下記に調査の詳細をご報告いたします。
問題が発生していた記事に関しては
赤色
、何かしら修正点がある場合は
青色
としました。
■ フリーソフト
オンスクリーンボリュームビューア
MFC アプリであるため一切問題はない。
■ クラスライブラリ
[LIB-001] CSystemTray システムトレイ管理クラス
[LIB-002] CColorPicker 色選択コンボボックスクラス
[LIB-003] CFontComboBox フォント選択コンボボックスクラス
[LIB-004] CAppCheck 多重起動チェッククラス
[LIB-005] CGetCalendar カレンダー選択ポップアップクラス
全て MFC アプリであるため問題はない。
[LIB-006] CAnalogClock アナログ時計操作クラス
最近 Win32SDK 版を公開していたため問題が発生している可能性があると思っていたが、
実際には、BeginPaint() をきちんと使用しており従来配布プログラムのままで一切問題はない。
但し、
今後の拡張を見越して基本クラス
CBaseWindow
を汎用化した。
※ これは近々公表予定のグラフコントロールクラス(SDK)の公開のためである。
※ VALGUS 製 SDK コントロールクラスは基本ウィンドウクラスを通常は共通化する。
■ Programming Tips サンプルプログラム
baseMyWnd.zip
testLeftRight.zip
testWallPaper.zip
testWheel.zip
testXML.zip
MFC アプリであり、OnPaint 内で CPaintDC dc(this); を使用しているため問題はない。
testBitmapRgn.zip
基本的には MFC アプリであるため CPaintDC dc(this); で処理しており問題はない。
なお、リージョン作成関数内にて CreateCompatibleDC(NULL); を使用して HDC を作成しているが、
正しく DeleteDC(hDC); されており、こちらも問題はない。
testManageBullet.zip
Win32 コンソールアプリなので、そもそもメッセージ処理が発生しないため問題はない。
CBasWin1.zip
WM_PAINT を処理しておらず GetDC も使用していない。よって何も問題はない。
CBasWin2.zip
派生クラス CDerivedWindow オーバーライドプロシージャ関数内にて WM_PAINT を処理しており、
その関数内で GetDC(); 〜DeleteDC(); という大きなミスを発見。これを BeginPaint(); 〜 EndPaint(); に修正した。
なお、BeginPaint() としたことで描画範囲が正しくクリップされてサンプルの文字列が正常に表示されなくなった。
対処としてウィンドウをインスタンスした後で ::InvalidateRect(win1->GetSafeHwnd(), NULL, FALSE); とした。
※ きっとこのせいで GetDC() しちゃったのではないかと…
TestTrashBox.zip
WinMain 関数しか実装していないのと、画面描画に関する一切の処理を行っていないため問題はない。
testVector.zip
WM_PAINT メッセージ処理では、BeginPaint(); 〜 EndPaint(); で括られており問題はなかった。
CreateCompatibleDC(); で hMemDC を作成しているが、こちらも適切に DeleteDC(); で削除していた。
だが、別の問題が見つかった。
GetStockObject() で取得したブラシを DeleteObject() していた。
MSDN ヘルプによると
> DeleteObject 関数を呼び出してストックオブジェクトを削除する必要はありません(削除しても問題はありません)。
とあるため、
問題なしと判断する
が、気になる方は DeleteObject() を削除してください。
■ Programming Tips サイト内記事情報
MFC Tips
念のため、全てチェックし直したが、元々 WM_PAINT がラップされているため全く問題はない。
その他、現状私が知り得る範囲内で問題はない。
Win32SDK Tips
→ [SDK-001] ビットマップからリージョンを作る
特に問題となる記述はない。
基本ベースが MFC で構成されているためだが、
SDK サンプルに MFC ベースもどうかと思う
…のは別問題であるため今回はパス。
※ 時間が取れれば CBaseWindow ベースにプログラムを変更する予定。
→ [SDK-004] ウィンドウクラスを構築する Part.1 (コールバックをクラス内で記述する)
WM_PAINT問題は Part.2 に情報が引き継がれるため、本ページ自体には問題はない。
→ [SDK-005] ウィンドウクラスを構築する Part.2 (複数インスタンスに対応する)
プロシージャ関数内 WM_PAINT 処理において BeginPaint(); 〜 EndPaint(); を使用していない。
それだけではなく GetDC() で取得した共通デバイスコンテキストをあろう事か DeleteDC() という間違いプログラムの表記あり。
適切な処理に書き換えるとともに、問題箇所の理由とお詫び文章と掲載した。
→ [SDK-002] マウスやキーボードを自動で動かす
→ [SDK-003] 正確な時間計測を行う
→ [SDK-006] Beep を制御する
→ [SDK-007] ファイルをゴミ箱に移動する
画面描画に関する処理ではないため問題はない。
Game Tips
→ [GAME001] 弾などの高速な管理方法(リスト構造)
アルゴリズム解説であるため全く問題はない。
→ [GAME002] フレームレートを固定化したゲームループの作り方
メッセージループ処理の解説であるため問題はない。
→ [GAME003] ベクトルによる移動処理(初級編)
→ [GAME004] ベクトルによる移動処理(中級編)
数値演算などの数学的解説であるため問題はない。
…唯一の問題は上級編のネタが書けない
ということだ(爆)。
DirectX Tips
→ [DX8-001] 能動的にホイールの状態を取得する
DirectInput の解説であるため問題はない。
→ [DX8-002] 相手が右にいるのか左にいるのかを判断する
D3DXライブラリを使用した数値演算処理の解説であるため問題はない。
Copyright 2005 VALGUS. All Rights Reserved.