【问题标题】:Is it possible to replace a method at runtime?是否可以在运行时替换方法?
【发布时间】:2015-04-15 08:37:03
【问题描述】:

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

有些答案说函数指针,但是定义的函数或类呢?

像这样:

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

有没有办法获取或替换它的函数指针?

顺便说一句,挂钩不被视为答案,因为它非常特定于平台且很危险。

【问题讨论】:

  • C 和 C++ 是不同的语言。
  • 这是可能的,而且是特定于平台的。例如,它用于 Linux 内核 (ksplice)。它与可执行格式有关,而不是与语言有关。 PS:你想达到什么目标?
  • @ibre5041:Linux 内核不使用 C++,这大大简化了事情。例如,没有(运算符)重载。

标签: c++ function-pointers


【解决方案1】:

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

您可以通过使用polymorphism 或任何其他方式来替换该方法的作用来配置一个对象。

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

【讨论】:

    【解决方案2】:

    运行时函数“替换”可以通过以下几种技术之一来实现:

    1. Polymorphism
    2. 标准图书馆设施,如std::function
    3. 翻译或分派函数调用的第三方库或平台特定技术。

    哪个选项更好在很大程度上取决于预期用途和目标环境。例如;插件系统可以很好地利用多态性(使用适当的工厂,甚至可能与template method pattern 结合使用),而内部函数路由可以使用std::function

    这些技术不会真正“替换”任何函数,而是可以在运行时设置以根据需要路由函数调用。

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

    【讨论】:

      【解决方案3】:

      虽然你不能直接替换一个方法,但这可以通过另一层间接来解决。

      #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设为static(更好的是,将其设为返回静态变量的静态方法,以避免静态初始化顺序惨败)

      【讨论】:

      • 非常感谢
      【解决方案4】:

      回到最初的问题:“是否可以在 C/C++ 中在运行时替换方法”这是可能的,并且有一些用例,但是(正如其他人所说)这些用例中的大多数都没有不适用于你。这也不是很简单。

      例如 linux kernell 可以使用名为 kpatchkGraft 的东西。这是一个相当复杂的机制——当然它不是很便携,在用户空间程序中也不是很实用,因为这些技术依赖于嵌入到 linux kernell 中的机制。

      【讨论】:

        【解决方案5】:

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

        C++11 中,假设要更改的方法是virtual,并假设您的C++ 实现使用位于对象开头的vtable 指针(在Linux 上GCC 经常出现这种情况) ,并且如果新旧类具有相同的大小并且使用来自公共基类的单一继承(例如FooBase),则可以使用放置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 覆盖旧的 vptr。

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

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

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

        【讨论】:

          【解决方案6】:

          还可以查看 MSVC 编译选项 /hotpatch。这将创建一个代码,其中每个非内联方法都以一条至少有 2 个字节的指令开始(short jmp relative 0 - 即 NOP)。然后你可以重写你正在运行的应用程序的图像,你可以将 long jmp 存储到你的方法的新版本中。

          Create Hotpatchable Image.

          另外,例如在 Linux 上,您有(很久以前有)两个名为“fopen”的函数。其中一个是在库 glibc 中定义的,另一个是在 libpthread 中定义的。后者是线程安全的。而当你对 libpthread 进行 dlopen 操作时,“fopen”函数上的“跳线”会被覆盖,并且会使用来自 libpthread 的函数。

          但这真的取决于你的目标。

          【讨论】:

          • 使用/ob0,内联被关闭,因此每个方法都可以被热补丁。缺点:vector::operator[ ] 也可以热补丁,这可能不是您想要的。
          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

            我猜这对于 C/C++ 来说是不可能的。

            函数代码已编译成二进制,运行时无法更改。

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

            【讨论】:

              猜你喜欢
              • 2010-10-13
              • 1970-01-01
              • 1970-01-01
              • 2019-09-09
              • 2010-09-19
              • 1970-01-01
              • 1970-01-01
              • 2019-08-24
              • 1970-01-01
              相关资源
              最近更新 更多