是否可以在运行时替换方法?

时间:2015-04-15 08:37:03

标签: c++ function-pointers

我想创建一个能够在运行时覆盖方法的插件系统。

有些答案说功能指针,但定义的函数或类怎么样?

像这样:

class foo
{
  public:
    bar(int foobar);
}

有没有办法为它获取函数指针,或者替换它?

顺便说一句,挂钩并不是一个答案,因为它具有特定的平台和危险性。

8 个答案:

答案 0 :(得分:9)

要制作插件系统,您无需在运行时替换类的方法。

您可以使用polymorphism配置对象的任何其他方式来替换方法的功能。

查看以下问题的答案:What's safe for a C++ plug-in system?

答案 1 :(得分:5)

运行时功能"替换"可以通过以下几种技术之一来实现:

  1. Polymorphism
  2. 标准库设施,例如std::function
  3. 翻译或分派函数调用的第三方库或平台特定技术。
  4. 哪个选项更好取决于预期用途和目标环境。例如;插件系统很可能利用多态(使用适当的工厂,甚至可能与template method pattern结合使用),而内部函数路由可以使用std::function

    这些技术不会真正替代"任何函数,但可以在运行时设置,以根据需要路由函数调用。

    注意我专注于问题的C ++方面(它标记为C和C ++,但示例代码是C ++)。

答案 2 :(得分:4)

虽然您无法直接替换方法,但可以使用另一层间接方法解决此问题。

#include <iostream>
#include <functional>

class Foo
{
private:
    void default_bar(int value)
    {
        std::cout << "The default function called\n";
    }
    std::function<void(Foo*, int)> the_function = &Foo::default_bar;
public:
    void replace_bar(std::function<void(Foo*, int)> new_func)
    {
        the_function = new_func;
    }
    void bar(int value)
    {
        the_function(this, value);
    }
    void baz(int value)
    {
        std::cout << "baz called\n";
    }
};

void non_member(Foo* self, int value)
{
    std::cout << "non-member called\n";
}

int main()
{
    Foo f;
    f.bar(2);
    f.replace_bar(&Foo::baz);
    f.bar(2);
    f.replace_bar(non_member);
    f.bar(2);
    f.replace_bar([](Foo* self, int value){ std::cout << "Lambda called\n"; });
    f.bar(2);
}

目前,这取代了实例的方法。如果要替换类的方法,请使the_function为静态(更好,使其成为静态方法,返回静态变量以避免静态初始化顺序失败)

答案 3 :(得分:2)

回到原来的问题:&#34;是否可以在C / C ++&#34;中运行时替换方法?它是可能的,并且有一些用例,但(正如其他人所说)大多数这些用例并不适用于你。这也不是很简单。

例如,linux kernell可以使用名为kpatchkGraft的内容。这是一个相当复杂的机制---当然不是很便携,并且在用户空间程序中不是很常用,因为这些技术依赖于linux kernell的机制。

答案 4 :(得分:1)

C没有任何方法(只有函数),所以你的问题在C中没有意义。

C++11中,假设要更改的方法是virtual,并假设您的C ++实现使用位于对象开头的vtable指针(通常是这种情况) Linux上的GCC,如果旧类和新类具有相同的大小并且使用来自公共基类的单一继承(例如FooBase),则可以使用placement new运算符,所以在你的主程序中:

class FooBase {
   virtual ~FooBase();
   virtual int bar(int); 
   /// etc
}

class ProgramFoo : public FooBase {
 virtual ~ProgramFoo();
 virtual int bar (int);
 /// other fields and methods
};

并在你的插件中:

class PluginFoo : public FooBase {
 virtual ~ProgramFoo();
 virtual int bar (int);
 /// other fields and methods
 static_assert(sizeof(PluginFoo) == sizeof(ProgramFoo), 
               "invalid PluginFoo size");
};

然后你可能会有一些像

这样的插件功能
extern "C" FooBase*mutate_foo(ProgramFoo*basep)
{
   basep->~ProgramFoo(); // destroy in place, but don't release memory
   return new(basep) PluginFoo(); // reconstruct in same place
}

它希望用新的覆盖旧的vptr。

这闻起来很糟糕,可能是undefined behavior根据C ++ 11标准,但可能适用于某些 C ++实现,并且肯定是特定于实现的。我不建议采用这种方式进行编码,即使有时可能会“工作”。

惯用的方法是使用成员函数指针或C ++ 11 closures

看起来您的插件架构设计错误。查看Qt plugins以获得一些好的灵感。

答案 5 :(得分:1)

另请参阅MSVC编译选项/ hotpatch。这将创建一个代码,其中每个非内联方法以具有至少2个字节(短jmp相对0 - 即NOP)的指令开始。然后,您可以重写正在运行的应用程序的图像,并且可以将长jmp存储到您的方法的新版本中。

请参阅Create Hotpatchable Image.

另外例如在Linux上你(很久以前有)两个函数叫做#34; fopen&#34;。其中一个是在库glibc上定义的,另一个是在libpthread中定义的。后者是线程安全的。当你开始使用libpthread时,&#34;跳线&#34;进入&#34; fopen&#34;函数被覆盖,并使用libpthread函数。

但这实际上取决于你的目标。

答案 6 :(得分:0)

在Unix系统(例如Linux)上,dlsym对于在C / C ++中在运行时加载函数或整个库非常有用。参见例如 http://www.tldp.org/HOWTO/C++-dlopen/thesolution.html

答案 7 :(得分:-1)

我想C / C ++是不可能的。

功能代码已经编译为二进制,你不能改变它催款运行时。

我认为解释性语言很擅长。