回调函数

如何做类的回调函数 前些日子用一个PIPE类封装了WINDOWS的录音放音设备,程序写得有点类似与操作系统的PV信号互锁机制,这里面需要将辅助录音现程采集到的 数据存储到Buffer,然后做一个回掉函数做处理,以下是几种实现回掉的方法,程序最后使用了THUNK机制,再说明这种机制之前。先澄清一下回掉函数 的概念。 所谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数。例如Win32下的窗口过程函数就是一个典型的回调函数。 一般说来,C不会自己调用B,C提供B的目的就是让S来调用它,而且是C不得不提供。由于S并不知道C提供的B叫甚名谁,所以S会约定B的接口规范(函数原型),然后由C提前通过S的一个函数R告诉S自己将要使用B函数,这个过程称为回调函数的注册,R称为注册函数。 1。传统的CALLBACK 传统SDK回调函数设计模式 Win32 SDK是这方面的典型例子,这类SDK的函数接口都是基于C语言的,SDK或者提供专门的注册函数,用于注册回调函数的地址,或者是在调用某个方法时才传 入回调函数的地址,回调函数的原型也由于注册函数中的函数指针定义而受到约束。 以Win32中的多媒体定时器函数为例,其原型为: MMRESULT timeSetEvent( UINT uDelay, // 定时器时间间隔,以毫秒为单位 UINT uResolution, LPTIMECALLBACK lpTimeProc, // 回调函数地址 DWORD dwUser, // 用户设定的数据 UINT fuEvent ); typedef void (CALLBACK TIMECALLBACK)(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2); typedef TIMECALLBACK FAR *LPTIMECALLBACK; 因此,用户定义的回调函数必须具有上面指定的函数原型,下面是回调函数的具体使用方法: #include "stdio.h" #include "windows.h" #include "mmsystem.h" // 多媒体定时器需要包含此文件 #pragma comment(lib, "winmm.lib") // 多媒体定时器需要导入此库 void CALLBACK timer_proc(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) // 定义回调函数 { printf("time out./n"); } int main() { UINT nId = timeSetEvent( 1000, 0, timer_proc, 0, TIME_CALLBACK_FUNCTION|TIME_PERIODIC); // 注册回调函数 getchar(); timeKillEvent( nId ); return 0; } 2。简单的实现类成员函数做CALLBACK的方法 C++出现后,CALLBACK函数提出了自身的局限性,因为CALLBACK函数是为C语言的函数设计的,而C++本身的类的this指针导致了回调C++的类成员函数不能得到其有效地址,解决这个局限其实也简单,基本方法有两种 1. 不使用成员函数,直接使用普通C函数,为了实现在C函数中可以访问类的成员变量,可以使用友元操作符(friend),在C++中将该C函数说明为类的友元即可。这种处理机制与普通的C编程中使用回调函数一样。 2. 使用静态成员函数,静态成员函数不使用this指针作为隐含参数,这样就可以作为回调函数了。静态成员函数具有两大特点:其一,可以在没有类实例的情况下 使用;其二,只能访问静态成员变量和静态成员函数,不能访问非静态成员变量和非静态成员函数。由于在C++中使用类成员函数作为回调函数的目的就是为了访 问所有的成员变量和成员函数,如果作不到这一点将不具有实际意义。解决的办法也很简单,就是使用一个静态类指针作为类成员,通过在类创建时初始化该静态指 针,如pThis=this,然后在回调函数中通过该静态指针就可以访问所有成员变量和成员函数了。这种处理办法适用于只有一个类实例的情况,因为多个类 实例将共享静态类成员和静态成员函数,这就导致静态指针指向最后创建的类实例。为了避免这种情况,可以使用回调函数的一个参数来传递this指针,从而实 现数据成员共享。这种方法稍稍麻烦,这里就不再赘述。 3 将类成员函数写成虚函数,直接访问虚函数表,得到函数地址,再传递给调用函数 (wang2012629@126.com 的 方法),简单介绍如下 比如说DialogBox(...); 例子如下: // TestCallbackDlg.h : header file class CTestCallbackDlg : public CDialog { // Construction public: virtual LRESULT DlgProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);//放在最前面. CTestCallbackDlg(CWnd* pParent = NULL); // standard constructor// TestCallbackDlg.cpp : implementation file // LRESULT CTestCallbackDlg::DlgProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_COMMAND: { TRACE("DlgProc %d/r/n",hWnd); if( wParam ==IDOK) { //AfxMessageBox("gfhdfhfgh");/*前后都不响应,MessageBox不消失?*/ ::MessageBox(hWnd,"HAHAHA","dfgdfgh",MB_OK); ::EndDialog(hWnd,IDOK); //AfxMessageBox("gfhdfhfgh"); } break; } case WM_CLOSE: { AfxMessageBox("gfhdfhfgh"); ::EndDialog(hWnd,IDOK); break; } } return ::DefWindowProc(hWnd, message, wParam, lParam); } //从虚函数表中获取函数地址. void CTestCallbackDlg::OnOK() { // TODO: Add extra validation here unsigned long pThis = (unsigned long) this; DLGPROC TempFuncAddr = NULL; _asm { mov eax,pThis; mov edx,dword ptr [eax]; mov eax, dword ptr [edx+0D8h]; mov TempFuncAddr,eax; } TRACE("CTestCallbackDlg %d/r/n",this->m_hWnd); //TempFuncAddr中就是函数LRESULT CTestCallbackDlg::DlgProc(...)的地址 DialogBox(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDD_ABOUTBOX), this->m_hWnd, (DLGPROC)TempFuncAddr); } 4。利用抽象类CWndProc //wndpro.h #ifndef __WNDPROC_H__ #define __WNDPROC_H__ class CWndProc { protected: //保护的构造函数,必须由派生类来构造。 CWndProc(); virtual ~CWndProc(); protected: //窗口回调过程,基类作为纯虚函数没有实现代码。 virtual LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) = 0; private: //Hook代码块。 char m_hook[40]; protected: //m_pfnWndProc指针指向Hook代码块的始地址。 //注册窗口类(WNDCLASSEX),或者子类化控件窗口,或者DialogBox显示对话框 //等需要窗口回调过程参数时,使用m_pfnWndProc作为参数。 WNDPROC m_pfnWndProc; }; #endif //__WNDPROC_H__ //end of file 二、实现代码文件 //wndproc.cpp #include "stdafx.h" #include "wndproc.h" /* 全局的Hook代码,其C的伪代码为: LRSULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { return (CWndProc派生类的this指针)->WndProc(hwnd, uMsg, wParam, lParam); } 代码的功能就是直接转调用CWndProc派生类的WndProc。 */ static unsigned char g_hook[] = { 0x8B, 0x44, 0x24, 0x10, // mov eax,dword ptr [esp+10h] ; eax <- lParam 0x8B, 0x4C, 0x24, 0x0C, // mov ecx,dword ptr [esp+0Ch] ; ecx <- wParam 0x8B, 0x54, 0x24, 0x08, // mov edx,dword ptr [esp+8] ; edx <- uMsg 0x50, // push eax ; lParam 参数入栈 0x8B, 0x44, 0x24, 0x08, // mov eax,dword ptr [esp+8] ; eax <- hwnd 0x51, // push ecx ; wParam 参数入栈 0xB9, 0x00, 0x00, 0x00, 0x00, // mov ecx,0 ; ecx <- this指针,这里暂时用this(NULL), // ; 在类构造函数初始化时修改为实际类的this指针值 0x52, // push edx ; uMsg 参数入栈 0x50, // push eax ; hwnd 参数入栈 0x51, // push ecx ; this 参数入栈 0xE8, 0x00, 0x00, 0x00, 0x00, // call WndProc ; 调用派生类的WndProc,这暂时用0, // ; 在类构造函数初始化时修改为实际类虚拟表WndProc指针偏移值 0xC2, 0x10, 0x00 // ret 10h ; return }; CWndProc::CWndProc() { char *p; LRESULT (CALLBACK CWndProc::*pfn)(HWND, UINT, WPARAM, LPARAM); CopyMemory(m_hook, g_hook, sizeof(g_hook)); //把全局的Hook代码块,拷贝到类对象的Hook代码块 p = m_hook + 19; //p指针指向 mov ecx, 0 处,以便修改this(NULL)指针为实际类对象的this指针 *((unsigned int *)p) = (unsigned int)this; //修改p所指向的位置为mov ecx, (指向实际的类对象的this指针) pfn = WndProc; //pfn指向类虚拟表中WndProc函数的指针; p = m_hook + 27; //p指针指向 call WndProc处,以便修改WndProc虚表指针相对偏移值 //由于vc6.0无法修改pfn及强制其类型,所以下面使用几句汇编 __asm { mov eax, pfn ; eax <- pfn sub eax, 4 ; eax <- eax-4 mov edi, p ; edi <- p指针 sub eax, edi ; eax <- eax-edi 计算WndProc虚表指针与当前 EIP+5 相对偏移值 mov [edi], eax ; eax <- [edi] 修改p所指向的位置为 call (WndProc虚表指针与当前 EIP+5 相对偏移值) } m_pfnWndProc = (WNDPROC)&m_hook[0]; //把Hook代码块始址赋给m_pfnWndProc } CWndProc::~CWndProc() { } 五。导入类指针地址作为成员函数参数typedef void (*HELP_CALLBACK)(const char*, unsigned long); // 回调函数指针 void help_me( const char* question, HELP_CALLBACK callback, unsigned long user_value ); // 接口声明 #endif//__SDK_H__ #include "sdk.h" #include "stdio.h" #include "windows.h" HELP_CALLBACK g_callback; unsigned long g_user_value; void do_it() { printf("thinking.../n"); Sleep( 3000 ); printf("think out./n"); printf("call him./n"); g_callback( "2.", g_user_value ); // 将用户设定的数据传入 } void help_me( const char* question, HELP_CALLBACK callback, unsigned long user_value ) { g_callback = callback; g_user_value = user_value; // 保存用户设定的数据 printf("help_me: %s/n", question); do_it(); } /// app.cpp (应用程序源文件) #include "sdk.h" #include "stdio.h" #include "assert.h" class App { public: App( const char* name ) : m_name(name) { } void ask( const char* question ) { help_me( question, got_answer, (unsigned long)this ); // 将this指针传入 } static void got_answer( const char* msg, unsigned long user_value ) { App* pthis = (App*)user_value; // 转换成this指针,以访问非静态数据成员 assert( pthis ); printf("%s got_answer: %s/n", pthis->m_name, msg); } protected: const char* m_name; }; int main() { App app("ABC"); app.ask( "1+1=?"); return 0; } 六:最酷的方法,THUNK机制(感谢北理论坛的detrox ,这个方法太棒了) 在VC++中,所有类的成员函数在定义的时候都被隐式(implicit)定义为__thiscall参数传递方式。在MSDN 中对__thiscall做了如下定义: 引用: The __thiscall calling convention is used on member functions and is the default calling convention used by C++ member functions that do not use variable arguments. Under __thiscall, the callee cleans the stack, which is impossible for vararg functions. Arguments are pushed on the stack from right to left, with the this pointer being passed via register ECX, and not on the stack, on the x86 architecture. 其中心思想是,__thiscall 修饰的函数参数从右至左依次压入堆栈,被调用者负责平衡堆栈。之后是与C语言所有参数传递方式均不相同的一点:成员函数所在类的this指针被存入ecx寄存器(这个特性只针对Intel x86架构)。 对比之后,我们发现类成员函数不能作为回调函数的主要原因在于类成员函数使用__thiscal参数传递方式,因此需要调用者(caller)通过ecx寄存器提供类对象的指针。而回调函数使用__stdcall参数传递方式,不具备这个特点。 如何让类成员函数成为回调函数 ------------------------------------------------------------------ 根据第一节对回调函数与类成员函数各自特点的分析。不难发现,只要能想办法在类成员函数被调用之前设置好ecx寄存器,就能在__stdcall调用的基础上模拟出一个完好的__thiscall调用。 如 何提前设置ecx寄存器呢?我们知道函数调用实际是通过汇编指令(oprand)’call 函数地址’完成的。因此我们可以提供一个中间函数。当回调发生时,先调用中间函数,再在中间函数执行过程中设置ecx寄存器,当ecx设置好后jmp到类 成员函数去(注意:这里是jmp不是call)。当执行到类的成员函数时,函数上下文(function context)就和__thiscall所产生的完全一样了。 如何制作这个中间函数呢?普通的函数是不行的。主要因为在vc++ debug版本的代码中要使用ecx寄存器做堆栈溢出检测(stack overflow detect),即使是空函数都是如此。其次由于存在栈框(stack frame)效率也不高。 这时就需要使用thunk来达到我们的目的。所谓thunk就是程序自己生成并执行的一小段汇编代码。下面通过代码来理解thunk。 thunk实现: 引用: 以下代码在Windows XP SP2, Visual Studio 2005 Team Suite 下调试成功 所用处理器 Intel Pentium M 750, Dothan Core cpp 代码 [复制到剪贴板]#include "windows.h" #include "stdio.h" #include "stdlib.h" #include "assert.h" #include "stdafx.h" // // 回调函数类型定义 typedef int (CALLBACK *pfaCallBack)(int, long, char); / // thunk 结构定义 // 由于thunk 要被当作代码来执行,因此thunk 结构必须是字节对齐的,这里使用 // VC++ 的修饰符号#pragma pack(push, 1) 来定义一个字节对齐的结构体 // 之后通过#pragma(pop) 恢复默认对齐模式 #pragma pack(push, 1) struct Thunk { BYTE op_movecx; DWORD_PTR val_ecx; BYTE op_call; DWORD_PTR val_address; }; #pragma pack(pop) // // 一个类的定义,就这样平静的开始了 class Dummy { // 一个成员变量 private: int m_id ; // 定义一个thunk private: Thunk m_thunk; // 定义构造函数,在构造函数中设置m_id值 public: Dummy(int id):m_id(id) { } / // 定义一个回调函数,另外他还是个类的成员函数呢 public: int memberCallback(int intVal, long longVal, char charVal) { // 做自己想做的事情 printf("/nI am a member function of class Dummy" "(Dummy::memberCallback),ID = %d." "/nI got the value 0x%08x 0x%08x /'%c/'" , m_id, intVal, longVal, charVal); return m_id; } // // 初始化thunk 的数据,这里是关键 public: void InitThunk() { // 0xB9是'mov ecx, 数值’的机器码,xB9之后的个字节(32位)指定了要 // 给ecx的数值. m_thunk.op_movecx = 0xB9; // 填写要给ecx的数值为this(类对象的指针) m_thunk.val_ecx = (DWORD_PTR)this; // 0xE9是'jmp 相对地址’的机器码。相对地址由xE9之后的个字节(32位) // 给出。 m_thunk.op_call = 0xE9; // 获得Dummy::memberCallback的具体地址。关于成员函数与类对象的关系 // 请参阅Stan Lippman 的< > // 用汇编获得地址省去了用C++带来的难看的语法 DWORD_PTR off = 0; _asm { mov eax, Dummy::memberCallback mov DWORD PTR [off], eax } // jmp后面是相对地址,因此要求出这个地址 // 相对地址=成员函数地址-跳转点下一指令地址 // 正负号不要紧,jmp自己能根据正负判断如何跳。 m_thunk.val_address = off - ( (DWORD_PTR)(&m_thunk.val_address) + sizeof(DWORD_PTR) ); } // // 返回thunk的地址给要回调他的函数。 // 那个函数还以为thunk是一个函数地址呢。根本不知道thunk是我们自己构造的 // 数据 public: pfaCallBack GetStaticEntry() { return (pfaCallBack)&m_thunk; } }; // // 一个调用回调函数的函数 void Trigger(pfaCallBack callback) { assert(callback); int intVal = 0x1234; int longVal = 0x5678ABCD; int charVal = 'D'; // 函数内部 int r; // 开始回调 r = callback(intVal, longVal, charVal); printf("/n Return value = %d/n", r); } // // 传说中的主函数。VC++工程里生成的就叫_tmain不叫main。 int _tmain(int argc, _TCHAR* argv[]) { //生成一个对象 Dummy *dummy1 = new Dummy(9); //初始化thunk dummy1->InitThunk(); //取得thunk地址 pfaCallBack pCallback1 = dummy1->GetStaticEntry(); //给需要回调函数的函数传递thunk Trigger(pCallback1); // 按任意键继续... system("pause"); return 0; } cpp 代码 pragma once #include #include // BYTE Alignment #pragma pack(push, 1) struct ThunkData { struct { BYTE opcode; DWORD_PTR oprand; } movecx; struct { BYTE opcode; DWORD_PTR oprand; } jmp; }; #pragma pack(pop) template class CThunk { public: CThunk(); public: ~CThunk(void); public: void Attach(DWORD_PTR owner, PointerType PointerToMemberFunction); public: void * GetThunk() { assert(bAttached == true);return &m_thunkData;}; public: operator CallbackType() {return (CallbackType)(&m_thunkData);}; private: PointerType m_fPointer; private: ThunkData m_thunkData; private: bool bAttached; }; template CThunk ::CThunk(): bAttached(false) { // make sure that this is stored in ecx #ifdef _DEBUG DWORD_PTR pthis; _asm { mov dword ptr [pthis],ecx }; assert(pthis == (DWORD_PTR)this); #endif // mov ecx, this m_thunkData.movecx.opcode = 0xB9; m_thunkData.jmp.opcode = 0xE9; } template CThunk ::~CThunk(void) { } template void CThunk ::Attach(DWORD_PTR owner, PointerType PointerToMemberFunction) { assert(owner); assert(PointerToMemberFunction); m_fPointer = PointerToMemberFunction; DWORD_PTR FunctionOffset; _asm { mov eax, PointerToMemberFunction mov FunctionOffset, eax } m_thunkData.jmp.oprand = FunctionOffset - ( (DWORD_PTR)&m_thunkData.jmp.oprand + sizeof(DWORD_PTR) ); m_thunkData.movecx.oprand = owner; bAttached = true; } 使用方法 cpp 代码 [ class Dummy { private: int m_id ; // // Dummy Thunk public: CThunk DummyThunk; CThunk DummyThunk2; // // Default constructor public: Dummy():m_id(0) { DummyThunk.Attach((DWORD_PTR)this, &Dummy::memberCallback); DummyThunk2.Attach((DWORD_PTR)this, &Dummy::memberCallback2); } .... ... Trigger(dummy1->DummyThunk); Trigger((pfaCallBack)dummy2->DummyThunk.GetThunk()); Trigger(dummy3->DummyThunk2); ... cpp 代码 #define THUNK(NAME,FUNCTION) private: ThunkData m_##NAME##THUNK;/ public:/ void * NAME##Thunk() {/ DWORD_PTR dwptr;/ m_##NAME##THUNK.oprMOV = (DWORD_PTR)this;/ __asm {mov eax, FUNCTION}/ __asm {mov dwptr, eax}/ m_##NAME##THUNK.oprJMP = dwptr - (((DWORD_PTR)&m_##NAME##THUNK.oprJMP) + sizeof(DWORD_PTR));/ return &m_##NAME##THUNK;/ };/ 使用方法 class Dummy { THUNK(memberCallback, Dummy::memberCallback); THUNK(AnotherCallback, Dummy::AnotherCallback); private: int m_id ; .... cpp 代码 Trigger((pfaCallBack)dummy1->memberCallbackThunk()); Trigger((pfaCallBack)dummy2->AnotherCallbackThunk()); 转自 黄申的博客 http://blog.sina.com.cn/qqhuangshen
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值