魔法王座神格在哪开启:C++中实现类回调的办法
来源:百度文库 编辑:九乡新闻网 时间:2024/07/14 02:08:26
当我们使用普通函数做为回调函数时, 一般格式如下:
typedef int (*pFunc) (int , int) ;
但是如果要使用类成员函数做为回调函数时, 我们想到的格式会是下面的样子:
typedef int (*CClassName::Func) (int, int) ;
如是经过类型检查时, 会如愿收到如下的错误:
error C2350: 'CClassName::Func' is not a static member
这是因为普通成员函数和静态成员函数在内存中的位置是不一样的. 普通成员函数在代码区该类的代码区间中 , 而静态成员函数则和普通函数一样存储在数据区, 只是比普通函数多了一个作用域区分符而已. 问题就是: 有办法使用普通成员函数做为回调函数使用吗?
解决办法:
答案是肯定的. 但是要做到这点必须先解决两个问题: 一是如何获取函数的地址并传递到无类型指针中去,或者其他类型指针. 主要是要去掉作用域区分符.二是获取了函数地址之后如何调用函数.
第一个问题的解决中间运用了编译器的一个技巧. 我们知道所有的转换或者匹配必须经过类型检查, 除了使用可变参数的函数之外. 这样我们就可以把 CClassName::Func 以可变参数的形式转换成指针, 从而获取除去类对象地址之后的函数偏移地址.
第二个问题问题就是如何调用函数的问题. 我们知道所有类对象都隐藏了一个指象对象自己的this指针. 只要我们把类对象的地址传递过来, 我们就有了对象的基址, 加上上面获得的偏移就可以完全访问类成员函数了. 我们知道一般函数的调用约定都属于 __stacall, 这种调用约定是先将调用者入栈,然后是地址, 然后是参数从右至左依次入栈. 这样我们使用一段汇编代码就可以手动完成函数的调用了. 将类对象指针存入ECX寄存器, 然后依次压入参数.
关键代码:
第一个问题的解决代码如下:
void* OffsetToVoid( void* Dummy, ... )
{
va_list list;
void* RetVal;
va_start( list, Dummy );
RetVal = va_arg( list, void* );
va_end( list );
return RetVal;
}
将函数偏移地址以可变参数的形式传递进来, 避过类型检查.
第二个问题的解决代码:
int Call( void* pObj,
void* pOffset,
int arg1,
int arg2 )
{
__asm
{
mov ecx,[pObj];
push arg2;
push arrg1;
call [pOffset];
}
}
将参数压栈, 并调用函数 .
使用方法:
CClassName obj;
int nRet;
nRet = Call( &obj,
OffsetToVoid(0, CClassName::Func)
value1,
value2);
PS: 类成员函数也必须满足__stdcall(Pascall)式调用约定. 附件中为源代码。
typedef int (*pFunc) (int , int) ;
但是如果要使用类成员函数做为回调函数时, 我们想到的格式会是下面的样子:
typedef int (*CClassName::Func) (int, int) ;
如是经过类型检查时, 会如愿收到如下的错误:
error C2350: 'CClassName::Func' is not a static member
这是因为普通成员函数和静态成员函数在内存中的位置是不一样的. 普通成员函数在代码区该类的代码区间中 , 而静态成员函数则和普通函数一样存储在数据区, 只是比普通函数多了一个作用域区分符而已. 问题就是: 有办法使用普通成员函数做为回调函数使用吗?
解决办法:
答案是肯定的. 但是要做到这点必须先解决两个问题: 一是如何获取函数的地址并传递到无类型指针中去,或者其他类型指针. 主要是要去掉作用域区分符.二是获取了函数地址之后如何调用函数.
第一个问题的解决中间运用了编译器的一个技巧. 我们知道所有的转换或者匹配必须经过类型检查, 除了使用可变参数的函数之外. 这样我们就可以把 CClassName::Func 以可变参数的形式转换成指针, 从而获取除去类对象地址之后的函数偏移地址.
第二个问题问题就是如何调用函数的问题. 我们知道所有类对象都隐藏了一个指象对象自己的this指针. 只要我们把类对象的地址传递过来, 我们就有了对象的基址, 加上上面获得的偏移就可以完全访问类成员函数了. 我们知道一般函数的调用约定都属于 __stacall, 这种调用约定是先将调用者入栈,然后是地址, 然后是参数从右至左依次入栈. 这样我们使用一段汇编代码就可以手动完成函数的调用了. 将类对象指针存入ECX寄存器, 然后依次压入参数.
关键代码:
第一个问题的解决代码如下:
void* OffsetToVoid( void* Dummy, ... )
{
va_list list;
void* RetVal;
va_start( list, Dummy );
RetVal = va_arg( list, void* );
va_end( list );
return RetVal;
}
将函数偏移地址以可变参数的形式传递进来, 避过类型检查.
第二个问题的解决代码:
int Call( void* pObj,
void* pOffset,
int arg1,
int arg2 )
{
__asm
{
mov ecx,[pObj];
push arg2;
push arrg1;
call [pOffset];
}
}
将参数压栈, 并调用函数 .
使用方法:
CClassName obj;
int nRet;
nRet = Call( &obj,
OffsetToVoid(0, CClassName::Func)
value1,
value2);
PS: 类成员函数也必须满足__stdcall(Pascall)式调用约定. 附件中为源代码。
C++中实现类回调的办法
ANSI C 字符串库函数的实现
嵌入式系统中FFT算法研究与实现(C语言)
Delphi中PING的实现
实现你的梦想的办法就是:每天抽出一小时 .
实现你的梦想的办法就是:每天抽出一小时.
实现你的梦想的办法就是:每天抽出一小时
只言片语中c的人生哲理
基于Hi2011的DVB-C机顶盒设计与实现
ARM Bootloader的实现 ---C和ASM混合编程
11嵌入式系统中FFT算法研究与实现(C语言)
[c、c++]宏中"#"和"##"的用法(zz)
Qt中菜单和快捷键的实现
SAP中寻找增强的实现方法
C语言中sizeof的用法总结
C语言中常见的英语单词缩写
C语言中offsetof宏的应用
实现你的梦想的办法就是:每天抽出一小时20111016
实现你的梦想的办法就是:每天抽出一小时0
C语言的那些小秘密之变参函数的实现
Linux C 编程 实现彩色文字输出 - wesleyluo的专栏 - CSDN博客
[C#]实现IEnumerable接口来使用foreach语句的一个实例
视频监控笔记——用C 实现的远程监控系统概述
潘凯:C 对象布局及多态实现的探索(十二)