Ghetto Closures in C++ III: Templates and Traits and Interfaces, oh my!
Last time, we went over generating a tiny ASM thunk that could wrap a C++ method pointer (instance plus function) up
in a non-method __stdcall
function pointer, suitable for use as, say, a win32 WndProc. Next, I’m going to talk about
wrapping it all up in a convenient API. To do this, we’re going to need some template-fu.
Since I am using a modern compiler, I am going to see how close I can get to boost.function‘s interface:
boost::function<void ()> fn = &someFunction;
I consider this interface to be pretty rad.
boost::function
works with only a single template argument, so we could go that route too. We could also accept that we’re doing something a bit different, and add a second parameter:
Thunk<LRESULT (Window::*)(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)> wndProcThunk;
or
Thunk<Window, LRESULT (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)> wndProcThunk;
I picked the first one, mostly because it was the first thing that popped into my head. Here is my test harness:
#include <cstdio>
using std::printf;
struct I {
virtual void print() = 0;
virtual void printSum(double y) = 0;
};
struct C : I {
C(int x)
: x(x)
{ }
void print() {
printf("My x is %in", x);
}
void printSum(double y) {
printf("%i + %f = %fn", x, y, x + y);
}
int x;
};
void main() {
C instance(4);
Thunk<void (I::*)()> thunk(&instance, &I::print);
printf("n");
thunk.get()();
Thunk<void (I::*)(double)> thunk2(&instance, &I::printSum);
printf("n");
thunk2.get()(3.14);
}
First, I extracted the part of the code that actually constructs and cleans up the generated code. Everything else I’ll outline will just be scaffolding so that the interface is prettier.
template <typename D, typename S>
D really_reinterpret_cast(S s) {
char __static_assert_that_types_have_same_size[sizeof(S) == sizeof(D)];
union {
S s;
D d;
} u;
u.s = s;
return u.d;
}
template <typename C, typename M>
void* createThunk(C* instance, M method) {
char code[] = {
0xB9, 0, 0, 0, 0, // mov ecx, 0
0xB8, 0, 0, 0, 0, // mov eax, 0
0xFF, 0xE0 // jmp eax
};
// YEEHAW
*((I**)(code + 1)) = instance;
*((void**)(code + 6)) = really_reinterpret_cast<void*>(method);
void* thunk = VirtualAlloc(0, sizeof(code), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(thunk, &code, sizeof(code));
FlushInstructionCache(GetCurrentProcess(), thunk, sizeof(code));
return thunk;
}
void releaseThunk(void* thunk) {
VirtualFree(thunk, 0, MEM_RELEASE);
}
To be honest, this interface isn’t all that bad: all you have to do is remember to manage the lifetime of the generated code and cast the void* you get to the right type. That’s kind of boring, though, so let’s instead see if we can make something kickass and typesafe.
I like objects, so let’s start with one of those.
template <typename M>
struct Thunk {
typedef M Method;
typedef typename methodptr_traits<M>::class_type Class;
typedef typename methodptr_traits<M>::function_type Function;
Thunk(Class* instance, Method method)
: ptr(createThunk(instance, method))
{ }
~Thunk() {
releaseThunk(ptr);
}
Function get() const {
return reinterpret_cast<Function>(ptr);
}
private:
Thunk(const Thunk&);
void* ptr;
};
This is simple enough to be boring, except for this methodptr_traits thing.
methodptr_traits is an instance of something called a traits class. Basically, it is a fancy template type that defines various other types. You can think of it as a way to code ad-hoc, compile-time type introspection.
If you’ve never used templates this way, the implementation is pretty intimidating:
template <typename F>
struct methodptr_traits;
template <typename ReturnType, typename T>
struct methodptr_traits<ReturnType (T::*)()> {
typedef T class_type;
typedef ReturnType (__stdcall *function_type)();
};
This is one of the more convoluted things one can do with templates, and I’ll be the first to admit that I think it’s a bit scary. Let’s rewind a bit and look at this in simpler terms. Say we want a boolean variable that’s true if a particular template type is a number. We can use template specialization to accomplish this pretty easily:
template <typename T> struct is_int { enum {value = false}; };
template <> struct is_int<int> { enum {value = true}; };
template <> struct is_int<short> { enum {value = true}; };
template <> struct is_int<char> { enum {value = true}; };
// and so on
I might leverage this code with something like the following:
if (is_int<T>::value) { /* stuff */ }
and be off to the races.
Cool, right? Now what if we instead wanted to know whether something is a std::vector, whatever the element type? The same principle applies, but now we have a template specialization that is itself a template:
template <typename T> struct is_vector { enum {value=false}; };
template <typename E> struct is_vector<std::vector<E> > { enum {value=true}; };
How to use this should be obvious:
if (is_vector<T>::value) { /* do something that only works on std::vector */ }
methodptr_traits
is just a tiny jump further. Most of the terror that this sort of thing inspires is really the fault of C++’s ridiculous function pointer syntax.
It is kind of a drag that C++ templates cannot express functions without specifying exactly how many arguments the function has. Because of this, a new specialization must be written for each argument count you want to support. I only did 0 and 1 arguments because this is just a small example. boost tends to support a minimum of 10 arguments by default, which is good enough for almost everyone.
For this example, I only need 0 and 1 argument, so here’s the specialization for a one-argument function:
template <typename ReturnType, typename T, typename Arg1>
struct methodptr_traits<ReturnType (T::*)(Arg1)> {
typedef T class_type;
typedef ReturnType (__stdcall *function_type)(Arg1);
};
And that’s it! With a single templatized class, we can dynamically generate an assembly thunk that works as a perfectly usable __stdcall
function. We can pass this function pointer on to Win32, GLU, or whatever other C library we might need a callback function for.