是否可以在运行时替换方法?
Is it possible to replace a method at runtime?
我想制作一个能够在运行时覆盖方法的插件系统。
一些答案说函数指针,但是定义的函数或class?
像这样:
class foo
{
public:
bar(int foobar);
}
有没有办法获取或替换它的函数指针?
顺便说一句,挂钩不被认为是一个答案,因为它非常特定于平台并且很危险。
要制作插件系统,您不需要在运行时替换 class 的方法。
您可以使用 polymorphism 或任何其他方式 配置 对象来替换该方法的作用。
查看以下问题的答案:What's safe for a C++ plug-in system?
我猜 C/C++ 是不可能的。
函数代码已经编译成二进制,催款运行时不能更改。
我认为解释性语言擅长于此。
C没有任何方法(只有函数),所以你的问题在C中没有意义。
在 Linux 上的 C++11, assuming the method to be changed is virtual
, and assuming your C++ implementation is using a vtable pointer located at start of the object (this is often the case with GCC 中,并且如果新旧 class 具有相同的大小并且使用来自公共基础 class 的单一继承(例如 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++ 实现,并且肯定是特定于实现的。我不建议以这种方式编码,即使 "work".
有时可能会发生这种情况
惯用的方法是使用成员函数指针或 C++11 closures。
看来您的插件架构设计有误。查看 Qt plugins 以获得一些好的灵感。
运行时功能 "replacement" 可以通过以下几种技术之一实现:
- Polymorphism
- 标准图书馆设施,例如
std::function
- 翻译或调度函数调用的第三方库或平台特定技术。
哪个选项更好在很大程度上取决于预期用途和目标环境。例如;插件系统可以很好地利用多态性(使用适当的工厂,甚至可能与 template method pattern 结合使用),而内部函数路由可以使用 std::function
.
这些技术不会真正 "replace" 任何函数,而是可以在运行时设置以根据需要路由函数调用。
注意我专注于问题的 C++ 方面(它被标记为 C 和 C++,但示例代码是 C++)。
虽然您不能直接替换方法,但这可以通过另一层间接来解决。
#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);
}
目前这取代了实例的方法。如果要替换 class 的方法,请将 the_function
设置为静态(更好的是,将其设置为返回静态变量的静态方法以避免静态初始化顺序失败)
回到最初的问题:"Is it possible to replace a method at runtime in C/C++"这是可能的,并且有一些用例,但是(正如其他人所说)大多数这些用例并不适用于您。这也不是很简单。
例如 linux kerell 可以使用名为 kpatch or kGraft 的东西。这是一个相当复杂的机制 --- 它当然不是很便携,并且在用户空间程序中不是很有用,因为这些技术依赖于 linux 内核中的机制。
另请查看 MSVC 编译选项 /hotpatch。这将创建一个代码,其中每个非内联方法都以至少具有 2 个字节的指令开始(短 jmp 相对 0 - 即 NOP)。然后你可以重写你的 运行 应用程序的图像,你可以将 long jmp 存储到你的新版本的方法中。
例如,在 Linux 上,您有(很久以前就有)两个名为 "fopen" 的函数。其中一个是在库 glibc 中定义的,另一个是在 libpthread 中定义的。后者是线程安全的。当您 dlopen-ed libpthread 时, "jumper" 到 "fopen" 函数被覆盖,并使用 libpthread 中的函数。
但这真的取决于你的目标。
在 Unix 系统上(例如 Linux),dlsym 对于在运行时在 C/C++ 中加载函数或整个库非常有用。参见例如
http://www.tldp.org/HOWTO/C++-dlopen/thesolution.html
我想制作一个能够在运行时覆盖方法的插件系统。
一些答案说函数指针,但是定义的函数或class?
像这样:
class foo
{
public:
bar(int foobar);
}
有没有办法获取或替换它的函数指针?
顺便说一句,挂钩不被认为是一个答案,因为它非常特定于平台并且很危险。
要制作插件系统,您不需要在运行时替换 class 的方法。
您可以使用 polymorphism 或任何其他方式 配置 对象来替换该方法的作用。
查看以下问题的答案:What's safe for a C++ plug-in system?
我猜 C/C++ 是不可能的。
函数代码已经编译成二进制,催款运行时不能更改。
我认为解释性语言擅长于此。
C没有任何方法(只有函数),所以你的问题在C中没有意义。
在 Linux 上的 C++11, assuming the method to be changed is virtual
, and assuming your C++ implementation is using a vtable pointer located at start of the object (this is often the case with GCC 中,并且如果新旧 class 具有相同的大小并且使用来自公共基础 class 的单一继承(例如 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++ 实现,并且肯定是特定于实现的。我不建议以这种方式编码,即使 "work".
有时可能会发生这种情况惯用的方法是使用成员函数指针或 C++11 closures。
看来您的插件架构设计有误。查看 Qt plugins 以获得一些好的灵感。
运行时功能 "replacement" 可以通过以下几种技术之一实现:
- Polymorphism
- 标准图书馆设施,例如
std::function
- 翻译或调度函数调用的第三方库或平台特定技术。
哪个选项更好在很大程度上取决于预期用途和目标环境。例如;插件系统可以很好地利用多态性(使用适当的工厂,甚至可能与 template method pattern 结合使用),而内部函数路由可以使用 std::function
.
这些技术不会真正 "replace" 任何函数,而是可以在运行时设置以根据需要路由函数调用。
注意我专注于问题的 C++ 方面(它被标记为 C 和 C++,但示例代码是 C++)。
虽然您不能直接替换方法,但这可以通过另一层间接来解决。
#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);
}
目前这取代了实例的方法。如果要替换 class 的方法,请将 the_function
设置为静态(更好的是,将其设置为返回静态变量的静态方法以避免静态初始化顺序失败)
回到最初的问题:"Is it possible to replace a method at runtime in C/C++"这是可能的,并且有一些用例,但是(正如其他人所说)大多数这些用例并不适用于您。这也不是很简单。
例如 linux kerell 可以使用名为 kpatch or kGraft 的东西。这是一个相当复杂的机制 --- 它当然不是很便携,并且在用户空间程序中不是很有用,因为这些技术依赖于 linux 内核中的机制。
另请查看 MSVC 编译选项 /hotpatch。这将创建一个代码,其中每个非内联方法都以至少具有 2 个字节的指令开始(短 jmp 相对 0 - 即 NOP)。然后你可以重写你的 运行 应用程序的图像,你可以将 long jmp 存储到你的新版本的方法中。
例如,在 Linux 上,您有(很久以前就有)两个名为 "fopen" 的函数。其中一个是在库 glibc 中定义的,另一个是在 libpthread 中定义的。后者是线程安全的。当您 dlopen-ed libpthread 时, "jumper" 到 "fopen" 函数被覆盖,并使用 libpthread 中的函数。
但这真的取决于你的目标。
在 Unix 系统上(例如 Linux),dlsym 对于在运行时在 C/C++ 中加载函数或整个库非常有用。参见例如 http://www.tldp.org/HOWTO/C++-dlopen/thesolution.html