Skip to content

Instantly share code, notes, and snippets.

@DrPizza
Created August 29, 2012 03:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DrPizza/3506435 to your computer and use it in GitHub Desktop.
Save DrPizza/3506435 to your computer and use it in GitHub Desktop.
Capturing lambdas converted to function pointers.
#include <SDKDDKVer.h>
#include <Windows.h>
#include <memory>
#include <functional>
template<typename R, typename... A>
struct thunk {
thunk(std::function<R(A...)> fn) : the_thunk(new thunk_holder(fn)) {
}
typedef R(__stdcall *stdcall_type)(A...);
typedef R(__fastcall *fastcall_type)(A...);
typedef R(__cdecl *cdecl_type)(A...);
stdcall_type as_stdcall() const {
return the_thunk->as_stdcall();
}
fastcall_type as_fastcall() const {
return the_thunk->as_fastcall();
}
cdecl_type as_cdecl() const {
return the_thunk->as_cdecl();
}
private:
struct thunk_holder {
thunk_holder(std::function<R(A...)> f_) : f(f_), page(nullptr) {
generate_thunk();
}
~thunk_holder() {
::VirtualFree(page, 0, MEM_RELEASE);
}
stdcall_type as_stdcall() const {
size_t offset(reinterpret_cast<const char*>(&fn_2_stdcall) - reinterpret_cast<const char*>(&fn_1_start));
return reinterpret_cast<stdcall_type>(static_cast<char*>(page) + offset);
}
fastcall_type as_fastcall() const {
size_t offset(reinterpret_cast<const char*>(&fn_3_fastcall) - reinterpret_cast<const char*>(&fn_1_start));
return reinterpret_cast<fastcall_type>(static_cast<char*>(page) + offset);
}
cdecl_type as_cdecl() const {
size_t offset(reinterpret_cast<const char*>(&fn_4_cdecl) - reinterpret_cast<const char*>(&fn_1_start));
return reinterpret_cast<cdecl_type>(static_cast<char*>(page) + offset);
}
private:
#pragma optimize("", off)
#pragma runtime_checks("", off)
#pragma check_stack(off)
#pragma strict_gs_check(push, off)
static void fn_1_start() {
}
static R __stdcall fn_2_stdcall(A... args) {
std::function<R(A...)>* f(reinterpret_cast<std::function<R(A...)>*>(static_cast<size_t>(0xdeadbeefbaadf00d)));
// force an indirect call, otherwise the compiler will do a relative call that won't work when the code is copied
auto fn = &std::function<R(A...)>::operator();
return (f->*fn)(args...);
}
static R __fastcall fn_3_fastcall(A... args) {
std::function<R(A...)>* f(reinterpret_cast<std::function<R(A...)>*>(static_cast<size_t>(0xdeadbeefbaadf00d)));
auto fn = &std::function<R(A...)>::operator();
return (f->*fn)(args...);
}
static R __cdecl fn_4_cdecl(A... args) {
std::function<R(A...)>* f(reinterpret_cast<std::function<R(A...)>*>(static_cast<size_t>(0xdeadbeefbaadf00d)));
auto fn = &std::function<R(A...)>::operator();
return (f->*fn)(args...);
}
static void fn_5_end() {
}
#pragma strict_gs_check(pop)
#pragma check_stack
#pragma runtime_checks("", restore)
#pragma optimize("", on)
void generate_thunk() {
const size_t function_size(reinterpret_cast<const char*>(&fn_5_end) - reinterpret_cast<const char*>(&fn_1_start));
page = ::VirtualAlloc(nullptr, function_size, MEM_COMMIT, PAGE_READWRITE);
std::memcpy(page, reinterpret_cast<const char*>(&fn_1_start), function_size);
char* start(static_cast<char*>(page));
for(size_t i(0); i < function_size; ++i) {
if(*reinterpret_cast<size_t*>(start + i) == static_cast<size_t>(0xdeadbeefbaadf00d)) {
*reinterpret_cast<size_t*>(start + i) = reinterpret_cast<size_t>(&f);
}
}
DWORD old_protection(0);
::VirtualProtect(page, function_size, PAGE_EXECUTE_READ, &old_protection);
::FlushInstructionCache(::GetCurrentProcess(), page, function_size);
}
std::function<R(A...)> f;
void* page;
};
std::shared_ptr<thunk_holder> the_thunk;
};
template<typename R, typename... A>
thunk<R, A...> make_thunk(std::function<R(A...)> f) {
return thunk<R, A...>(f);
}
// motivating example
struct window {
window() : wndproc([this](HWND wnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT { return this->window_procedure(wnd, msg, wp, lp); }) {
WNDCLASSEX clazz = {0};
clazz.cbSize = sizeof(WNDCLASSEX);
clazz.lpszClassName = L"window-class";
clazz.lpfnWndProc = wndproc.as_stdcall();
clazz.style = CS_DBLCLKS;
clazz.hInstance = ::GetModuleHandle(NULL);
cls = ::RegisterClassEx(&clazz);
wnd = ::CreateWindow(clazz.lpszClassName, L"Window", WS_OVERLAPPEDWINDOW, 100, 100, 640, 480, 0, 0, ::GetModuleHandle(NULL), nullptr);
::ShowWindow(wnd, SW_RESTORE);
}
~window() {
::UnregisterClass(reinterpret_cast<const wchar_t*>(cls), 0);
}
int pump_messages() {
MSG msg = {0};
while(BOOL rv = ::GetMessageW(&msg, NULL, 0, 0)) {
if(rv == -1) {
return -1;
}
::TranslateMessage(&msg);
::DispatchMessageW(&msg);
}
return static_cast<int>(msg.wParam);
}
private:
LRESULT window_procedure(HWND w, UINT message, WPARAM wp, LPARAM lp) {
// NB regular method, not static.
// NB no need to pass 'this' via WM_NCCREATE/CREATESTRUCTW
switch(message) {
default:
return ::DefWindowProc(w, message, wp, lp);
}
}
thunk<LRESULT, HWND, UINT, WPARAM, LPARAM> wndproc;
ATOM cls;
HWND wnd;
};
int main()
{
window w;
return w.pump_messages();
}
@Madji
Copy link

Madji commented Aug 29, 2012

Hm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment