// 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() {}