2

我通过取消引用来调用 vmt 上的虚拟方法,直到获得指向该方法的指针。

这一切都很好,但是,我将如何完全更改指向对象上 VM 表的指针?

例子:

聚丙烯; // 指向它的默认 VM 表

聚丙烯乙; //指向一个完全不同的VM表

A->MethodOne() // 如上所述的调用

B->MethodOne() // 调用完全不同的方法,因为我们将其指向 VM 表的指针覆盖为具有不同方法指针的备用表

我将如何做到这一点?

我的代码:

#include <Windows.h>
#include <iostream>

class PP
{
public:
    PP() { }
    ~PP() { }

    virtual void MethodOne() { std::cout << "1" << std::endl; }
    virtual void MethodTwo() { std::cout << "2" << std::endl; }
};

typedef void (*MyFunc)(void);

int main()
{
    PP* A = new PP();

    //(*(void(**)(void))(*(DWORD*)A + (4*1)))();
                      
    ( *(MyFunc*) ( *(DWORD*)A + (4*0) ) )(); // call index 0 (4bytes*0)
    A->MethodOne();
    A->MethodTwo();
    system("PAUSE");
    delete A;
    return 0;
}
4

2 回答 2

4

由于派生另一个类的常用方法对您不起作用,因此我可以想到三种解决方案。

  1. 更改 vtable 指针。这是不可移植的,并且有很多方法会出错。假设 vtable 位于类的开头(它用于 WinAPI 中的简单类),您可以将该指针替换为指向您自己的表的指针。

    *(void **)A = newVtable;
    

使用适当的指向成员函数的指针定义 newVtable。您必须非常小心地进行设置。它还可能搞乱删除和异常处理。

  1. 创建自己的 vtable。使用所需的指向方法函数的指针定义一个类,然后在您的类中定义一个指向其中之一的指针。然后,您可以根据需要更改指向表的指针。尽管您可以定义其他成员函数来隐藏丑陋的代码,但这在调用时会更加冗长。

    class vtable;
    
    class PP {
    public:
        PP();
        ~PP() { }
    
        void MethodOne() { std::cout << "1" << std::endl; }
        void MethodTwo() { std::cout << "2" << std::endl; }
    
        const vtable *pVtable;
    };
    
    class vtable {
    public:
        void (PP::*MethodOne)();
    };
    
    vtable One = {&PP::MethodOne};
    vtable Two = {&PP::MethodTwo};
    
    PP::PP(): pVtable(&One) { }
    
    void main() {
        PP* A = new PP();
    
    
        A->pVtable = &One;
    
        // call with
        (A->*(A->pVtable->MethodOne))();    // calls MethodOne
    
        A->pVtable = &Two;
        (A->*(A->pVtable->MethodOne))();    // calls MethodTwo
    }
    

(使用 VS2015 社区编译和测试)。这将是便携且安全的。

  1. 在类中定义方法指针,并单独更新它们。
于 2015-08-19T00:27:54.897 回答
0

如果我正确理解您的问题 - 您想在运行时替换对象的 VM 表。不确定为什么要使用 C++ 语言进行如此低级的修改?

无论如何,Microsoft C/C++ 支持称为“裸”调用约定的东西(与“stdcall”、“fastcall”等相反,它们在参数传递给函数的方式/顺序以及它们是否在堆栈上传递方面有所不同或在寄存器中)。在裸调用约定中,您可以完全控制传递参数的方式 - 您编写自己的内联汇编代码片段,负责将内容放入堆栈和堆栈展开。

https://msdn.microsoft.com/en-us/library/5ekezyy2.aspx

例如,您可以对构造函数使用裸调用约定(如果编译器不会抱怨)并将新 VM 表作为“隐藏”参数传递,就像“this”参数是传递给的“隐藏”参数一样成员函数(在“thiscall”调用约定中)。你可以在内联汇编中发挥你的魔力来替换构造函数中的虚拟机。

这整个事情看起来像是一个可怕的、脆弱的想法,因为改变其内部结构的新版本的编译器(通常不会暴露给你)可能会破坏你的代码。如果你真的需要某种动态机制来选择调用什么方法,听起来你应该实现自己的机制,而不是捎带在 C++ 的 VMT 机制之上。

于 2015-08-19T00:16:39.307 回答