// Written in the D programming language. /** * Forwards function pointers to delegates. * Parameters are converted between calling conventions. * * Currently supports: * * extern(Windows) function -> extern(D) delegate * * extern(C) function -> extern(D) delegate * * extern(D) function -> extern(D) delegate * * Author: * Daniel Murphy (yebblies@gmail.com) * * Adapted from: * www.codeproject.com/KB/cpp/thunk32.aspx?msg=2168100 * * See site for license * */ module yebblies.thunk; version(Windows) { import core.sys.windows.windows : VirtualAlloc, VirtualFree, FlushInstructionCache, MEM_COMMIT, PAGE_EXECUTE_READWRITE, MEM_DECOMMIT, GetCurrentProcess; } else version(posix) { import core.sys.posix.sys.mman : mmap, PROT_WRITE, PROT_EXEC, MAP_PRIVATE, MAP_FAILED, munmap; } else { static assert(0); } class Thunk(string linkage, U : R delegate(P), R, P...) if (linkage == "Windows" || linkage == "C" || linkage == "D") { public: static if (linkage == "Windows") { extern(Windows) R function(P) ptr; } else static if (linkage == "C") { extern(C) R function(P) ptr; } else static if (linkage == "D") { extern(D) R function(P) ptr; } else { static assert(0); } private: align(1) struct ThunkCode { static if (linkage != "D") { ubyte mov_eax; void* this_ptr; } ubyte mov_ecx; void* func_ptr; ubyte mov_edx; void* tt_ptr; ushort jmp_edx; }; static if (linkage == "Windows") { extern(Windows) static R ThunkThis(P p) { U fdg = void; asm { mov [fdg], EAX; mov [fdg+4], ECX; }; return fdg(p); } } else static if (linkage == "C") { extern(C) static R ThunkThis(P p) { U fdg = void; asm { mov [fdg], EAX; mov [fdg+4], ECX; }; return fdg(p); } } else static if (linkage == "D") { U dgstore; extern(D) static R ThunkThis(P p) { U* dgptr = void; asm { mov [dgptr], ECX; }; return (*dgptr)(p); } } public: this(U dg) { version(Windows) { auto tc = cast(ThunkCode*)VirtualAlloc(null, ThunkCode.sizeof, MEM_COMMIT, PAGE_EXECUTE_READWRITE); } else { auto p = mmap(null, ThunkCode.sizeof, PROT_WRITE | PROT_EXEC, MAP_PRIVATE, 0, 0); assert(p != MAP_FAILED); auto tc = cast(ThunkCode*)p; } static if (linkage == "Windows" || linkage == "C") { // Put ptr and fptr in some registers that aren't touched by the // function call, then jump to the handler above. // Indirect jump using edx // // mov eax, thisptr // mov ecx, funcptr // mov edx, &ThunkThis // jmp edx tc.mov_eax = 0xB8; tc.this_ptr = dg.ptr; tc.mov_ecx = 0xB9; tc.func_ptr = dg.funcptr; tc.mov_edx = 0xBA; tc.tt_ptr = &ThunkThis; tc.jmp_edx = 0xE2FF; } else { // Linkage == "D" // We can't touch eax because D uses it for passing parameters sometimes. // Instead store the delegate information inside this class instance, and pass a ptr in ECX // mov eax, thisptr // mov ecx, funcptr // mov edx, &ThunkThis // jmp edx dgstore = dg; tc.mov_ecx = 0xB9; tc.func_ptr = &dgstore; tc.mov_edx = 0xBA; tc.tt_ptr = &ThunkThis; tc.jmp_edx = 0xE2FF; } version(Windows) { FlushInstructionCache(GetCurrentProcess(), tc, ThunkCode.sizeof); } else { } ptr = cast(typeof(ptr))tc; //writefln("%d", tc.lea_ecx);//, *cast(ushort*)(ptr)); } ~this() { version(Windows) { VirtualFree(ptr, ThunkCode.sizeof, MEM_DECOMMIT); } else { munmap(ptr, ThunkCode.sizeof); } } }; /** * Creates a Thunk object * * Example: ---------------- import std.stdio; void main() { class C { int a; void func(int b) { writeln("a == ", a, "\nb == ", b); } } auto c = new C(); c.a = 7; auto th = thunk!"C"(&c.func); // th.ptr is extern(C) void function(int) th.ptr(3); // prints a == 7, b == 3 } --------------- */ Thunk!(linkage, U, R, P) thunk(string linkage, U : R delegate(P), R, P...)(U dg) if (linkage == "Windows" || linkage == "C" || linkage == "D") { return new Thunk!(linkage, U, R, P)(dg); } unittest { class C { float receiver(int n, int b, real c) { return (n + b) * c; } }; auto g = 12.f; float fn(int n, real b, float c) { return (n + b) * c + g; } auto c = new C(); version(Windows) { auto th1 = thunk!"Windows"( &c.receiver); auto th2 = thunk!"Windows"( &fn); } auto th3 = thunk!"C"( &c.receiver); auto th4 = thunk!"C"( &fn); auto th5 = thunk!"D"( &c.receiver); auto th6 = thunk!"D"( &fn); version(Windows) { assert(th1.ptr(3, 7, 10.f) == 100.f); assert(th2.ptr(3, 7354.L, 10.f) == (3 + 7354.L) * 10.f + 12.f); } assert(th3.ptr(3, 7, 10.f) == 100.f); assert(th4.ptr(3, 7354.L, 10.f) == (3 + 7354.L) * 10.f + 12.f); assert(th5.ptr(3, 7, 10.f) == 100.f); assert(th6.ptr(3, 7354.L, 10.f) == (3 + 7354.L) * 10.f + 12.f); class D { int func(int a) { return a + 17; } } auto d = new D; auto th7 = thunk!"D"(&d.func); assert(th7.ptr(12) == 29); } void main() {}