对类成员进行特殊操作(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,演示了“静态成员函数+对象的静态指针”做回调函数,不过在这里得注意一点,后面这种做法,整个类只能定义一个对象(因为对象的静态指针)。

CH341A编程器是一款广泛应用的通用编程设备,尤其在电子工程和嵌入式系统开发领域中,它被用来烧录各种类型的微控制器、存储器和其他IC芯片。这款编程器的最新版本为1.3,它的一个显著特点是增加了对25Q256等32M芯片的支持。 25Q256是一种串行EEPROM(电可擦可编程只读存储器)芯片,通常用于存储程序代码、配置数据或其他非易失性信息。32M在这里指的是存储容量,即该芯片可以存储32兆位(Mbit)的数据,换算成字节数就是4MB。这种大容量的存储器在许多嵌入式系统中都有应用,例如汽车电子、工业控制、消费电子设备等。 CH341A编程器的1.3版更新,意味着它可以与更多的芯片型号兼容,特别是针对32M容量的芯片进行了优化,提高了编程效率和稳定性。26系列芯片通常指的是Microchip公司的25系列SPI(串行外围接口)EEPROM产品线,这些芯片广泛应用于各种需要小体积、低功耗和非易失性存储的应用场景。 全功能版的CH341A编程器不仅支持25Q256,还支持其他大容量芯片,这意味着它具有广泛的兼容性,能够满足不同项目的需求。这包括但不限于微控制器、EPROM、EEPROM、闪存、逻辑门电路等多种类型芯片的编程。 使用CH341A编程器进行编程操作时,首先需要将设备通过USB连接到计算机,然后安装相应的驱动程序和编程软件。在本例中,压缩包中的"CH341A_1.30"很可能是编程软件的安装程序。安装后,用户可以通过软件界面选择需要编程的芯片类型,加载待烧录的固件或数据,然后执行编程操作。编程过程中需要注意的是,确保正确设置芯片的电压、时钟频率等参数,以防止损坏芯片。 CH341A编程器1.3版是面向电子爱好者和专业工程师的一款实用工具,其强大的兼容性和易用性使其在众多编程器中脱颖而出。对于需要处理25Q256等32M芯片的项目,或者26系列芯片的编程工作,CH341A编程器是理想的选择。通过持续的软件更新和升级,它保持了与现代电子技术同步,确保用户能方便地对各种芯片进行编程和调试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值