如何让类的成员函数作为回调函数

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

如何让类的成员函数作为回调函数

为什么类(class)的成员函(member function)数不能作为回调函数(callback function)

首先来看看回调函数有怎样的特点。windows中,回调函都显式(explicit)使用CALLBACK修饰符(decorator)修饰 (decorated)。实际上CALLBACK就是_stdcall 参数传递方式(calling convention)的宏定义。MSDN中对__stdcall做了如下定义:

The __stdcall calling convention is used to call Win32 API functions. The callee cleans the stack, so the compiler makes vararg functions __cdecl. Functions that use this calling convention require a function prototype.

其中心思想是,__stdcall修饰的函数,参数从右至左依次压入堆栈,被调用者(callee)负责平衡堆栈(clean also called ‘stack unwinding handling’)。

下面来看看类的成员函数有怎样的特点。在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。

#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()

相关文档
最新文档