この記事は、WINDOWS の階層が深い場合に、MFC Windows アプリケーションでサイズ変更されたコントロールを再描画できない問題を解決するのに役立ちます。
元の製品バージョン: Visual C++
元の KB 番号: 2724997
現象
ウィンドウの階層が深い MFC アプリケーションは、入れ子になった子ウィンドウの WM_SIZE
を受信できません。 その結果、ウィンドウの入れ子階層が x64 の特定の深さを超えると、コントロールのサイズは変更されません。
原因
この問題の根本原因は、深く入れ子になったウィンドウ階層を持つアプリケーションの設計アーキテクチャであるため、アプリケーションはカーネル スタック領域の設計上の制限に達します。 ユーザー モードでは、関数の詳細な再帰呼び出しが行われると、スタック オーバーフロー例外が発生します。 カーネル モードでも同じことが起こります。 ただし、カーネルはスマートで例外を処理し、何らかの方法でブルー スクリーンを回避しながら、アプリケーションの実行を許可する必要があります。
入れ子になったウィンドウ階層が深い MFC アプリケーションを考えてみましょう。 ここで、誰かがウィンドウのサイズを変更しようとしているので、その子ウィンドウもサイズを変更する必要があることを検討してください。 ここで、 SetWindowPos
API のような関数を使用してウィンドウのサイズを変更します。 WM_SIZE
がメッセージ キューに送信され、ウィンドウに関連付けられているウィンドウ プロシージャが呼び出されます。 MFC では、 CWnd::OnSize()
などのオーバーロードが呼び出されます。 ウィンドウは入れ子になっているため、すべての子ウィンドウに対して再帰的に行う必要があります。
次の例では、ラッパー CMyChildView1
CMyView
子ウィンドウがあることがわかります。
// Resize the Child View 1
void CMyView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
m_ChildView1.MoveWindow(0, 0, cx, cy);
}
// Resize the Child View 2
void CMyChildView1::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
// Resize list to fill the whole view.
m_ChildView2.MoveWindow(0, 0, cx, cy);
}
// Resize the Child View 3
void CMyChildView2::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
// Resize list to fill the whole view.
m_ChildView3.MoveWindow(0, 0, cx, cy);
}
呼び出し履歴の論理スナップショットの下のような呼び出し履歴が表示されます。
...[Snip]
CMyChildView3::OnSize()
USER32!UserCallWinProcCheckWow
USER32!DispatchClientMessage
USER32!__fnINLPWINDOWPOS
ntdll!KiUserCallbackDispatcherContinue
USER32!ZwUserSetWindowPos
CMyChildView2::OnSize()
USER32!UserCallWinProcCheckWow
USER32!DispatchClientMessage
USER32!__fnINLPWINDOWPOS
ntdll!KiUserCallbackDispatcherContinue
USER32!ZwUserSetWindowPos
CMyChildView1::OnSize()
USER32!UserCallWinProcCheckWow
USER32!DispatchClientMessage
USER32!__fnINLPWINDOWPOS
ntdll!KiUserCallbackDispatcherContinue
USER32!ZwUserSetWindowPos
CMyView::OnSize()
USER32!UserCallWinProcCheckWow
USER32!DispatchClientMessage
USER32!__fnINLPWINDOWPOS
ntdll!KiUserCallbackDispatcherContinue
USER32!ZwUserSetWindowPos
...[Snip]
これで、 SetWindowPos
呼び出しは、指定されたウィンドウの位置に変更を加えるためにカーネル モードに移行します。 その後、カーネル モードからユーザー モードへのコールバックが発生し、ウィンドウのウィンドウ プロシージャを呼び出して WM_WINDOWPOSCHANGING
または WM_WINDOWPOSCHANGED
メッセージを処理します。 メッセージが処理されると、 SetWindowPos
呼び出しが返されます。
階層が深いため、カーネル モードではスタックをある程度拡大することが制限され、深度レベルのチェックが行われます。 また、制限に達すると、ウィンドウの WM_SIZE
処理が無視され、問題が発生します。 これは Windows OS のバグではなく、アプリケーションが決して依存してはならない制限事項です。
解決方法
ウィンドウの階層が深く入れ子にならないように、アプリケーションを再設計します。
WM_SIZE
メッセージにPostMessage
を使用してみてください。 これによりブロック解除呼び出しが行われ、スタックを制限まで増やす必要はなくなり、多少非同期関数呼び出しが行われます。 SendMessage
の場合、メッセージが処理されるまで、呼び出し履歴は連続する入れ子になったウィンドウに対して拡張されるためです。
詳細
x86 でも発生する可能性があるため、この問題は x64 Windows に限定されません。 Windows では、x86 Windows で問題が発生するには、より深いウィンドウ階層が必要になります。 違いはなぜですか? ポインターのサイズは 32 ビットから 64 ビットに 2 倍になり、カーネル モード スタックのサイズは倍になりませんでした。