前置博文:
2010.10.3:《有关C++类成员函数的指针调用》
2012.12.18:《C++成员函数回调的一种实现》
本研究为XDL项目(Xiaofei Development Library)的一部分。
实现基于X86动态代码的函数委托,并提供基于模板类的代码中显式调用。
目前实现的功能:
1、除pascal外任何调用约定的C++类成员函数通过cdecl调用约定的C函数指针进行调用的适配器。
2、thiscall调用约定的C++类成员函数通过stdcall调用约定的C函数指针进行调用的适配器。
其中1多用于代码中调用的委托,2多用于系统API传入回调函数(如WndProc、ThreadProc)。因为目前仅做到了通过RTTI识别函数指针类型的调用约定,而未做到识别参数的个数与长度,如果要做到识别参数,RTTI的parser庞大到一个小型编译器的地步,并且我对文法分析已经忘的差不多了。所以对于以上两点之外的调用约定转换考虑到用到的机会几乎为零,暂时舍弃。
使用方法举例:
class Worker
{
public:
void ThreadProc(void *param)
{
return;
}
};
int main()
{
Worker worker;
Delegate<void(*)(void*)> callback(&Worker::ThreadProc, &worker);
callback((void*)12345);
_beginthread(callback, 0, (void*)12345);
for(;;);
return 0;
}
主要实现代码:
static HANDLE xdl_exec_heap_handle = NULL;
void* xdl_exec_heap_malloc(unsigned int size)
{
if (xdl_exec_heap_handle == NULL)
xdl_exec_heap_handle = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
return HeapAlloc(xdl_exec_heap_handle, 0, size);
}
void xdl_exec_heap_free(void *p)
{
HeapFree(xdl_exec_heap_handle, 0, p);
}
static char xdl_fn_adapter_get_callconv(const char *type_raw_name)
{
char *p = (char*)type_raw_name;
if (*p++ == '.' && *p++ == 'P')
{
if (*p == '6')
return *++p;
else if (*p++ == '8')
{
for ( ; !(*p == '@' && *(p + 1) == '@'); ++p);
return *(p + 3);
}
}
return -1;
}
void* xdl_fn_adapter(const void* fn, const void *obj, const type_info *_In_Type, const type_info *_Out_Type)
{
int size = 0;
char *buf = NULL;
int pos = 0;
if (obj == NULL) //c function
{
size = 5;
buf = (char*)xdl_exec_heap_malloc(size);
buf[pos++] = '\xe9'; //jmp
*(int*)&buf[pos] = (int)fn - ((int)&buf[pos] + 4); pos += 4;
}
else //c++ member function
{
char in_callconv = xdl_fn_adapter_get_callconv(_In_Type->raw_name());
char out_callconv = xdl_fn_adapter_get_callconv(_Out_Type->raw_name());
if (out_callconv == 'A') // cdecl
{
size = 45;
buf = (char*)xdl_exec_heap_malloc(size);
*(int*)&buf[pos] = '\x55\x8b\xec\x81'; //push ebp; mov ebp,esp; sub esp,100
*(int*)&buf[pos + 4] = '\xec\x00\x01\x00';
*(int*)&buf[pos + 8] = '\x00\x56\x8d\x75'; //push esi; lea esi,[ebp+8]
*(int*)&buf[pos + 12] = '\x08\x57\x8d\xbd'; //push edi; lea edi,[ebp-100]
*(int*)&buf[pos + 16] = '\x00\xff\xff\xff';
*(int*)&buf[pos + 20] = '\xb9\x40\x00\x00'; //mov ecx,40
*(int*)&buf[pos + 24] = '\x00\xf3\xa5\x5f'; //rep movsd; pop edi
*(short*)&buf[pos + 28] = '\x5e\x68'; pos += 30; //pop esi; push
*(int*)&buf[pos] = (int)obj; pos += 4;
if (in_callconv == 'E' || in_callconv == 'I') //thiscall or fastcall
buf[pos++] = '\x59'; //pop ecx
if (in_callconv == 'I')
buf[pos++] = '\x5a'; //pop edx
buf[pos++] = '\xe8'; //call
*(int*)&buf[pos] = (int)fn - ((int)&buf[pos] + 4); pos += 4;
*(int*)&buf[pos] = '\x8b\xe5\x5d\xc3'; pos += 4; //mov esp,ebp; pop ebp; retn
}
else if (in_callconv == 'E' && out_callconv == 'G') //thiscall to stdcall
{
size = 10;
buf = (char*)xdl_exec_heap_malloc(size);
buf[pos++] = '\xb9'; //MOV ECX
*(int*)&buf[pos] = (int)obj; pos += 4;
buf[pos++] = '\xe9'; //JMP
*(int*)&buf[pos] = (int)fn - ((int)&buf[pos] + 4); pos += 4;
}
}
return buf;
}
void xdl_fn_adapter_free(void *p)
{
xdl_exec_heap_free(p);
}
template<typename _CFn>
struct Delegate
{
public:
Delegate()
{
m_obj = NULL;
m_fn_in = NULL;
m_fn_out = NULL;
m_type_in = NULL;
}
Delegate(const Delegate& d)
{
m_obj = d.m_obj;
m_fn_in = d.m_fn_in;
m_fn_out = NULL;
m_type_in = d.m_type_in;
}
~Delegate()
{
if (m_fn_out)
xdl_fn_adapter_free(m_fn_out);
}
Delegate(_CFn fn)
{
new(this) Delegate();
bind(fn);
}
template<class _Fn>
Delegate(_Fn fn, void* obj)
{
new(this) Delegate();
bind(fn, obj);
}
void bind(_CFn fn)
{
m_fn_in = fn;
}
template<class _Fn>
void bind(_Fn fn, void *obj)
{
m_obj = obj;
m_fn_in = xdl_force_cast<void*>(fn);
m_type_in = &typeid(_Fn);
}
operator _CFn()
{
if (!m_fn_out)
{
m_fn_out = xdl_fn_adapter(m_fn_in, m_obj, m_type_in, &typeid(_CFn));
}
assert(m_fn_out);
return (_CFn)m_fn_out;
}
private:
const void* m_obj;
const void* m_fn_in;
void* m_fn_out;
const type_info *m_type_in;
};