Update: I've officially deprecated this class, in the face of growing popularity of non-x86 platforms and Windows XP SP2's "data execution prevention" technology. I strongly recommend against using this code in new projects.
However, I'm keeping the code available here, just because it's the coolest thing I've ever written. ;-)
So what if you've used this code in the past, and SP2's DEP is getting in the way? Or you need to support IA64? Best bet is to forego the x86 assembly code thunk, in favor of a simple, straightforward static member function, and accompanying static vector of HWNDs for the various dialogs in your app. Not glamorous, but hey, it works.
Ever try to encapsulate a dialog behind an in-proc COM object? If it's a modal dialog, easy. If it's a modeless dialog... not so easy! Your COM server code probably doesn't own the message pump -- in all likelihood, the client does. And the rules of Win32 dictate that message pumps call IsDialogMessage whenever modeless dialogs are visible/active, else your keyboard accelerators won't work. Bummer!
Even if you have access to your client's source code, if it's written in MFC or VB the message pump code will be buried under 6 feet of boilerplate macros, header files, or runtime library code.
But never fear -- with a little ATL-style thunking code, we can hook into the message pump, and call IsDialogMessage ourselves! (Note: This code is intended for use in ATL projects, but there's no reason it shouldn't play nicely with any Win32 C++ code.)
Mad props to Andrew Nosenko's AtlAux library for the inspiration to use ATL-style thunks to implement a closure around SetWindowsHookEx.
void ShowModelessDialogsWhilePumpingMessagesImproperly() { // Create a few modeless dialogs CTestDialog dlg1; CTestDialog dlg2; CTestDialog dlg3; dlg1.Create(GetDesktopWindow()); dlg2.Create(GetDesktopWindow()); dlg3.Create(GetDesktopWindow()); // Hook onto them CModelessDialogHook hook1(dlg1); CModelessDialogHook hook2(dlg2); CModelessDialogHook hook3(dlg3); // Pump messages, while waiting for the dlgs to go away... // Note that this message pump doesn't call IsDialogMessage(), // to properly dispatch keyboard/accelerator messages intended // for our modeless dialogs! MSG msg; while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg); }
#pragma once
#ifndef _M_IX86
#error "This code is implemented for x86 only."
#endif
#pragma pack(push, 1)
template <class TDerived>
class CThiscallThunk
{
BYTE m_mov; // opcode: mov ecx, [dword]
DWORD m_this; // dword value
BYTE m_jmp; // opcode: jmp
DWORD m_relproc; // relative jmp distance
public:
typedef void (TDerived::*TMFP)();
void InitThunk(TMFP method, const TDerived* pThis)
{
union { DWORD func; TMFP method; } addr;
addr.method = (TMFP)method;
m_mov = 0xB9;
m_this = (DWORD)pThis;
m_jmp = 0xE9;
m_relproc = addr.func - (DWORD)(this+1);
FlushInstructionCache(GetCurrentProcess(), this, sizeof(*this));
}
FARPROC GetThunk() const
{
_ASSERTE(m_mov == 0xB9);
return (FARPROC)this;
}
};
#pragma pack(pop)
class CModelessDialogHook : public CThiscallThunk<CModelessDialogHook>
{
private:
HHOOK m_hHook;
HWND m_hWnd;
public:
CModelessDialogHook() : m_hHook(0), m_hWnd(0)
{ }
CModelessDialogHook(HWND h)
{ Hook(h); }
~CModelessDialogHook()
{ Unhook(); }
void Hook(HWND h)
{
// Initialize the thunk
InitThunk((TMFP)HookProc,this);
// Install the hook
m_hHook = ::SetWindowsHookEx(WH_GETMESSAGE,(HOOKPROC)GetThunk(),0,GetCurrentThreadId());
// Remember the window handle
m_hWnd = h;
}
void Unhook()
{
if (m_hHook)
::UnhookWindowsHookEx(m_hHook);
m_hHook = 0;
m_hWnd = 0;
}
LRESULT HookProc(int iHookCode, WPARAM wParam, LPARAM lParam) // note: not static! sweet!
{
MSG* pMsg = reinterpret_cast<MSG*>(lParam);
// Intercept keystroke messages, and process them
if ((iHookCode >= 0) &&
(wParam == PM_REMOVE) &&
(pMsg->message >= WM_KEYFIRST) &&
(pMsg->message <= WM_KEYLAST) &&
(::IsDialogMessage(m_hWnd,pMsg)))
{
pMsg->message = WM_NULL;
pMsg->lParam = 0;
pMsg->wParam = 0;
return 0; // note: we are not calling the next hook
}
// Pass on everything else
return ::CallNextHookEx(m_hHook,iHookCode,wParam,lParam);
}
};
