ATL/WTLのメッセージマップについて
今日、WTL*1で書かれたアプリのデバッグをしておりました。
その関係で、ATLのメッセージ処理周りを調べていたんですが、その過程で(僕的には)かなり面白いコードを見ることができたので、それについて少し書いてみます。
C++で書かれたフレームワークの場合、大抵1つの親windowの実体が、1つの(C++)classのインスタンスに対応付けられています。
Window毎に、HWNDを保持する1つのc++クラスが生成される感じです。
class CWnd { public: LRESULT WindowProc( UINT message, WPARAM wParam, LPARAM lParam ); protected: HWND hWnd; };
さて、class に定義された CWnd::WindowProc内でメッセージ処理を行うには、
RegisterClass() で登録した ::WindowProc( HWND hWnd, ... ); にて、hWnd から 対応するthisポインタを取得する必要がある。
MFCではこれを、グローバル定義された CMapもどきを使ってメッセージ処理の度に hWnd から CWnd * を取得しているのですが、
ATL 場合は・・・・・・
template <class TBase /* = CWindow */, class TWinTraits /* = CControlWinTraits */> class CContainedWindowT : public TBase // Windowメッセージを最初に受信する関数がこれ。 static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { .... if(uMsg != WM_NCDESTROY) lRes = pThis->DefWindowProc(uMsg, wParam, lParam); .... } }
// そして、CallWindowProcで、メッセージを再postした後 LRESULT DefWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { return ::CallWindowProc(m_pfnSuperWindowProc, m_hWnd, uMsg, wParam, lParam); }
template <class TBase, class TWinTraits> LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { // ちょwwwwwwwwwww // HWND を CWindowImplBaseT< TBase, TWinTraits >* にキャストとかwwwwwwwww CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd; ....
::CallWindowProc 呼び出し時の m_hWnd には、確かに正規のWindowHandle が格納されている。
ところが
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc( HWND hWnd, ....
で受け取った hWnd は、正しいpThis が格納されている???
いやでも、少なくともWin32NativeAPIが気を聞かせてhWnd を pThis に置き換えてくれるわけないし・・・・
まてよ、、、
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc
これが呼ばれる直前に、スタックに積まれた引数を細工することができれば・・・・
SetWindowLongPtr(hWnd, GWLP_WNDPROC, ...
してるところを見てみる。
template <class TBase, class TWinTraits> BOOL CWindowImplBaseT< TBase, TWinTraits >::SubclassWindow(HWND hWnd) { result = m_thunk.Init(GetWindowProc(), this); WNDPROC pProc = m_thunk.GetWNDPROC(); WNDPROC pfnWndProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc); m_pfnSuperWindowProc = pfnWndProc; m_hWnd = hWnd; return TRUE; }
m_thunk ってすごく怪しい。サンクって名前からしてあからさまに怪しい!
変数ウォッチで中身を見てみる。
う、うわぁ・・・・
mov dword ptr [esp + 4h], pThis jmp 構造体の終端アドレスとCWindowImplBaseT< TBase, TWinTraits >::WindowProcアドレスの差分が格納されてるはず。
昔、OS作ってた時も、こんなテクを随所で見かけた気がしますが、まさかATLでこんなことやってたとは。