#include #include #include namespace thisThunkExample{ /*Lets create a few helper classes, just to make the code a bit more realistic.*/ //raiiPaint ensures that calls to BeginPaint and EndPaint match correctly. class raiiPaint : public ::PAINTSTRUCT{ /* see */ ::HWND m_hWnd; raiiPaint(const raiiPaint&); raiiPaint& operator=(const raiiPaint&); public: struct error{}; raiiPaint(::HWND hWnd) :m_hWnd(hWnd) { if (::BeginPaint(hWnd, this) == 0) throw error(); } ~raiiPaint() throw(){::EndPaint(m_hWnd, this);} }; //This class is hardly worth bothering with, though with a bit more work //(adding wrappers for more relevant API calls it could be quite useful. class atom{ ::ATOM value; public: atom(::ATOM val) :value(val){} operator ::ATOM() const throw(){return value;} operator ::LPTSTR() const throw(){return reinterpret_cast<::LPTSTR>(value);} static atom registerClass(::HINSTANCE instance, ::LPCTSTR className, ::UINT style, ::LPCTSTR menuName = 0, ::HCURSOR cursor = 0, ::HICON icon = 0, ::HICON smallIcon = 0, ::HBRUSH background = reinterpret_cast<::HBRUSH>(COLOR_WINDOW + 1), ::WNDPROC winProc = ::DefWindowProc) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = style; wcex.lpfnWndProc = winProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = instance; wcex.hIcon = icon; wcex.hCursor = cursor; wcex.hbrBackground = background; wcex.lpszMenuName = menuName; wcex.lpszClassName = className; wcex.hIconSm = smallIcon; return RegisterClassEx(&wcex); } }; //The horrid union_cast. See . template inline To union_cast(From fr) throw(){ union{ From f; To t; } uc; uc.f = fr; return uc.t; } #pragma warning(push) #pragma warning(disable : 4355) #if defined(_M_IX86) #pragma pack(push,1) //Our thunk object. templateclass winThunk{ typedef ::LRESULT (W::* WndProc)(::HWND, ::UINT, ::WPARAM, ::LPARAM); const BYTE m_mov; // mov ECX, m_this const W* m_this; // const BYTE m_jmp; // jmp m_relproc const ptrdiff_t m_relproc; // relative jmp public: winThunk(WndProc proc, W* obj) :m_mov(0xB9), m_this(obj), m_jmp(0xE9), m_relproc(union_cast(proc) - reinterpret_cast(this) - sizeof(winThunk)) { ::FlushInstructionCache(::GetCurrentProcess(), this, sizeof(winThunk)); } operator ::WNDPROC() const{ return reinterpret_cast<::WNDPROC>(this); } }; #pragma pack(pop) #else #error Only X86 supported #endif #pragma warning(pop) //Our example window class. This is pretty minimal, it just has the functionality //we need to make our point. class exWinCls{ winThunk m_thunk; //Our thunk object const ::HWND m_handle; //Window handle //The message handler called by the thunk. ::LRESULT wndProc (::HWND hWnd, ::UINT uMsg, ::WPARAM wParam, ::LPARAM lParam){ switch(uMsg){ case WM_DESTROY: ::PostQuitMessage(0);//Goodbye, cruel world. return 0; case WM_PAINT: try{ raiiPaint rp(hWnd); paint(rp);//The real work is done here. } catch(raiiPaint::error&){ /*we won't actually do anything about this, however note that it prevents an ill-advised paint() or EndPaint()*/ } return 0; default://All other messages have default behaviour. return ::DefWindowProc(hWnd, uMsg, wParam, lParam); } } public: //If it was a real general-purpose windows class, and we liked to do things //in a type-safe way then we might have enums where the API uses constants //from define macros. Here we have just one example. enum showWindowType{ hide = SW_HIDE, normal = SW_SHOWNORMAL, showNormal = normal, showMinimized = SW_SHOWMINIMIZED, showMaximized = SW_SHOWMAXIMIZED, maximize = showMaximized, showNoActivate = SW_SHOWNOACTIVATE, show = SW_SHOW, minimize = SW_MINIMIZE, showMinNoActive = SW_SHOWMINNOACTIVE, showNA = SW_SHOWNA, restore = SW_RESTORE, showDefault = SW_SHOWDEFAULT, forceMinimize = SW_FORCEMINIMIZE }; //Return the current message handler ::WNDPROC windowProcedure() const{ return reinterpret_cast<::WNDPROC>(::GetWindowLong(m_handle, GWL_WNDPROC)); } //Set the message handler, returning the previous. ::WNDPROC windowProcedure(::WNDPROC newProc){ return reinterpret_cast<::WNDPROC>(::SetWindowLong(m_handle, GWL_WNDPROC, reinterpret_cast<::LONG>(newProc))); } //We'll make this function virtual, it seems the sort of function that would //be suitable for overriding. Although we use (indirectly) the window handle //we don't pass it from the message handler - the advantage in efficiency //does not sufficiently offset the risk of weird behaviour should an //over-riding class pass through the wrong handle. virtual void paint(::PAINTSTRUCT& ps){ typedef std::basic_string<::TCHAR> tstring; static const tstring quote = TEXT("\"...it has been truly said that hackers have even more words ") TEXT("for equipment failures than Yiddish has for obnoxious people.\"") TEXT("\n\n\t-\u00A0jargon.txt"); ::RECT rcDraw = clientRect(); ::OffsetRect(&rcDraw, 5, 5); ::InflateRect(&rcDraw, -10, -10); ::DrawText(ps.hdc, quote.data(), static_cast(quote.length()), &rcDraw, DT_EXPANDTABS | DT_WORDBREAK); } #pragma warning(push) #pragma warning(disable : 4355) //Our constructor. Note that we are naughty once again and use the this //pointer in the initializer list - not something to do in code that is //meant to be even remotely portable. exWinCls(LPCTSTR lpClassName, LPCTSTR lpWindowName, HINSTANCE hInstance) :m_handle(::CreateWindow(lpClassName, lpWindowName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, 0)) ,m_thunk(wndProc, this){ windowProcedure(m_thunk); } #pragma warning(pop) //some simple and straight-forward wrappers. bool showWindow(showWindowType type){return ::ShowWindow(m_handle, type) != 0;} bool updateWindow(){return ::UpdateWindow(m_handle) != 0;} ::RECT clientRect() const{ ::RECT ret; ::GetClientRect(m_handle, &ret); return ret; } }; //I like putting this in a seperate function, though nobody else seems to. //Pump messages until the program ends. int pumpMessages(){ ::MSG msg; while (::GetMessage(&msg, 0, 0, 0)){ // You would most likely have this conditional on a call to ::TranslateAccelerator. // We don't here because we have no menu, and no accelerator table. ::TranslateMessage(&msg); ::DispatchMessage(&msg); } return static_cast(msg.wParam); } }; int APIENTRY _tWinMain(::HINSTANCE hInstance, ::HINSTANCE, ::LPTSTR, int nCmdShow){ using namespace thisThunkExample; //Create a rather plain window class. atom at = atom::registerClass(hInstance, TEXT("THISCALLTHUNK"), CS_HREDRAW | CS_VREDRAW, 0, ::LoadCursor(0, IDC_ARROW)); //create a window. exWinCls win(at, TEXT("thiscallthunk"), hInstance); //get it out there. win.showWindow(static_cast(nCmdShow)); win.updateWindow(); //pump those messages. return pumpMessages(); }