WNDPROC Thunks Using the thiscall Calling Convention
I ended my article about WNDPROC thunk objects by promising to write about how to write thunks that call into member functions that use the thiscall calling convention. However the changes needed to change the code from the previous article, and the benefits gained from doing so, were slight.
However I did promise. Also, after feedback from readers of the previous article I'm going to include a full (if rather pointless) example of code that uses the thunk.
Prerequisites
This article assumes that you've read the previous article on WNDPROC thunks. It would also help to have a basic idea about Visual C++ calling conventions, it's not essential but it make some of this a bit less cryptic.
A Note About 64-bit Windows
This code will not work with future 64-bit releases of Windows. If you have the latest version of Visual C++ (Visual C++.Net) then warnings may be generated about truncating casts if you have compiler option /Wp64 set (detect 64-bit Portability Issues). While the code here blocks some warnings there are no macros blocking these warnings (your compiler options ask for warnings about 64-bit poratability issues, the code has 64-bit portability issues; it follows that you should see the warnings).
However the macro that stops execution if the target processor isnt x86 should prevent this code from actually being accidently compiled into a 64-bit application.
Previously, on HackCraft…
At the end of the previous article we had the following code:
#pragma warning( push )
#pragma warning( disable : 4355 )
#if defined(_M_IX86)
#pragma pack(push,1)
template<typename W> class winThunk{
typedef ::LRESULT (CALLBACK W::* WndProc) (::UINT, ::WPARAM, ::LPARAM);
const DWORD m_mov; // mov dword ptr [esp+0x4], m_this
const W* m_this; //
const BYTE m_jmp; // jmp WndProc
const ptrdiff_t m_relproc; // relative jmp
public:
winThunk(WndProc proc, W* obj)
:m_mov(0x042444C7),
m_this(obj),
m_jmp(0xE9),
m_relproc(union_cast<char*>(proc) - reinterpret_cast<char*>(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 )
This allowed us to to bind messages to a window to a member function like WindowProc in this code:
class myWindowClass{
::HWND m_hWnd;
/* other code elided */
::LRESULT CALLBACK WindowProc(::UINT uMsg, ::WPARAM wParam, ::LPARAM lParam);
/* other code elided */
};
In this article we will change the thunk so that rather than binding to this member function we will bind to one using the thiscall calling convention that will look like this:
class myWindowClass{
/* other code elided */
::LRESULT WindowProc(::HWND hWnd, ::UINT uMsg, ::WPARAM wParam, ::LPARAM lParam);
/* other code elided */
};
What Will We Gain
To be honest not that much in most cases. There is a slight efficiency gain in the fact that we won't have to look at a member to find the window handle (in the first version we replaced the window handle with the this pointer). This is a bit of a mixed blessing however:
On the one hand it seems pointless to throw away the window handle only to obtain it through a pointer (unless optimisation prevents it accessing a member variable involves dereferencing the this pointer), especially in the not uncommon cases were all but a few messages will result in another message handler being called (either the ::DefWindowProc provided by the Windows API, the original handler in the case of Windows subclassing, or a parent class' handler in the case of object-orientated subclassing — yes, the different meanings attached to the word “subclassing” is annoying…).
On the other hand this interferes with the invariant offered by the class. It becomes possible to make it act upon the wrong window, by passing in the wrong handle. While this shouldn't happen in the case of our private message handler, it could happen in the case of functions it calls if we decide to pass them the handle (so they have the equivalent gain in efficiency) and those functions are public or, more likely, protected.
Really the pretty much negligible efficiency gain is not a good reason to choose this version over the previous. Of course if you are going to use one then you might as well go even the tiniest efficiency gain, all else being equal, but I certainly wouldn't change existing code from one version to the other.
Another possible gain is that this can allow us to have a member function handle messages for several different windows. The value of this is slight (generally we're back to the problem with associating applicaiton data with windows that the thunks are meant to fix in the first place). However this could be used in cases where several similar controls on a parent window need the same behaviour for most messages, and that behaviour is likely to change for the collectively.
Really the main reason I had for doing this in the first place is I wanted to see if I could.
Changing the Assembly
We began with code that copy a constant (a pointer to the object we are binding messages to) to the position in the stack occupied by the window handle. To use the thiscall convention we are going to want to copy it into the ECX register instead, leaving the ::HWND argument untouched.
The assembly for this looks like so:
mov ECX, THIS_POINTER
jmp WND_PROCEDURE
Where THIS_POINTER will be replaced with the pointer to the object in question and WNP_PROCEDURE will be the relative jump needed to arive at the start of the message handler function.
If you paid close attention to the last article you may be thinking “You said you didn't know assembly”. Okay, I exagerated a bit to make the assembly seem less intimidating, though really I don't know more than the basic concepts; this really is a simple piece of code.
Editing the thunk class to use this assembly gives us:
#pragma warning( push )
#pragma warning( disable : 4355 )
#if defined(_M_IX86)
#pragma pack(push,1)
template<typename W> class 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<char*>(proc) - reinterpret_cast<char*>(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 )
And that's pretty much it.
A full example
Okay, now for an example that shows all of this in action. The following is written as a single file to make copy-and-paste easier. You can also download it from here.
#include <windows.h>
#include <tchar.h>
#include <string>
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 <https://hackcraft.communicraft.com/raii/> */
::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 <https://hackcraft.communicraft.com/cpp/windowsThunk/>.
template<typename To, typename From> 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.
template<class W>class 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<char*>(proc) - reinterpret_cast<char*>(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<exWinCls> 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<int>(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<int>(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<exWinCls::showWindowType>(nCmdShow));
win.updateWindow();
//pump those messages.
return pumpMessages();
}