`
love19820823
  • 浏览: 944040 次
文章分类
社区版块
存档分类
最新评论

对类成员进行特殊操作(2) ---转贴

 
阅读更多
对类成员进行特殊操作(2)


发布者: 北斗龙 (进入北斗龙个人专栏)

评价等级:
代码下载
2位用户为此文章评分,平均分为4.0

大家在看了文章1后以经对取得虚函数地址有所了解,但用它作回调函数还是有一点问题,因为要使成员函数正确运行,我们必须每次在调用这前传一个this给ecx(对应的成员函数中没有操作类成员除外,原因见(文章1)),作为回调函数,它可不给你这个机会,那怎么实现呢?

下面就是一种实现方法:

我们可以在数据段中开一个数组,在这个数组里存放一些特殊的数据,然后将这个数组的地址作为回调函数的地址,然后这个数组将被作为代码运行。那么这个数组对应的代码又做些什么呢?很简单的:将对象的地址传入ecx, 然后jmp到真正的回调函数。

下面是个例子:

主要部分也就是ThunkInit(ThunkData t, void *This, int VirFucID);

传入一个数组指针,一个对象的指针,一个对应第几个虚函数。然后这个函数将生成对应的可执行代码:将对象的地址传入ecx, 最后jmp到真正的回调函数。

其次,就是用数组的地址作为函数地址,调用对应的函数时,先执行数组里的代码,这样就保证了函数内部的this指针的正确性。

例3

class CCC
{
public:
 CCC() {m_data1=50;}
 ~CCC() {};
 virtual void print1() { ::printf("m_data1=%dn", m_data1); }
 virtual void print2() { ::printf("m_data1+10=%dn", m_data1+10); }
 virtual void print3(int num) { ::printf("m_data1+%d=%dn", num, m_data1+num); }
private:
 int m_data1;
};

typedef unsigned char ThunkData[14];
void inline ThunkInit(ThunkData t, void *This, int VirFucID)
{ // begin ThunkInit
 t[0] = 0xB9; // mov ecx,
 *((long *)(t+1)) = (long) This; // this
*((short int *)(t+5)) = 0x018B; // mov eax, [ecx]
t[7] = 0x5; // add eax,
*((long *)(t+8)) = VirFucID*4; // VirFucID*4
*((short int *)(t+12)) = 0x20FF; // jmp [eax]
} // end ThunkInit
void print5Times1(long fucAddr)
{
int i=5;
 while(i--)
((void (__stdcall *)(void))fucAddr)();
}
void print5Times2(long fucAddr, int num)
{
int i=5;
 while(i--)
 ((void (__stdcall *)(int))fucAddr)(num);
}
void main()
{
 CCC cc;
 ThunkData fucAddr;
 //不做回调函数
 ThunkInit(fucAddr, &cc, 0);
 ((void (__stdcall *)(void))(long)fucAddr)();
 ThunkInit(fucAddr, &cc, 1);
 ((void (__stdcall *)(void))(long)fucAddr)();
 ThunkInit(fucAddr, &cc, 2);
 ((void (__stdcall *)(int))(long)fucAddr)(100);
 //做回调函数
 ThunkInit(fucAddr, &cc, 0);
 print5Times1((long)fucAddr);
 ThunkInit(fucAddr, &cc, 1);
 print5Times1((long)fucAddr);
 ThunkInit(fucAddr, &cc, 2);
 print5Times2((long)fucAddr, 100);
}

注意:

1. ((void (__stdcall *)(void))(long)fucAddr)();是将fucAddr的地址转换为long型,然后再将它转化为(void (__stdcall *)(void))类型函数(如有不明白,请找有关资料),最后调用此函数。
2. ThunkInit函数可能较难理解,下面是进一步的讲解:
t[0] = 0xB9; //这个操作码为 mov ecx, 它将把接下来的long立即数,送入ecx
*((long *)(t+1)) = (long) This; // this, 对应的对象的地址,在运行是将作立即数,送入ecx
*((short int *)(t+5)) = 0x018B; // mov eax, [ecx] 取得虚函数表的起始位置
t[7] = 0x5; // add eax,
*((long *)(t+8)) = VirFucID*4; //VirFucID*4 计算出存放对应虚函数地址的地址
*((short int *)(t+12)) = 0x20FF; // jmp [eax] 调转到以应的虚函数

最后我想加几句题外话,这些例子都只是简单的用应,文章1中两个例子没有实际意义,旨在为例3做铺垫。至于有没有用,我也不知,只是我在一些程序中应用了而己,刚开始只是从其它原码中拿过来用,也不解其中的原理。但一次在图书管中看了<编程深入引导>>(中国水利水电出版社)后,由于它详细说明了类的实现,并附了对应的汇编源码,让我豁然开朗,经过几翻测试,终于弄懂了它的原理,觉得做法挺不错,也就写了上面这些,文笔较差,还不知能否让大家看懂。

不过这种做法有点费涩难懂,在附件中的工程DD,演示了“静态成员函数+对象的静态指针”做回调函数,不过在这里得注意一点,后面这种做法,整个类只能定义一个对象(因为对象的静态指针)。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics