前置博文:
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; };