使用 Gcc 在运行时导入 C++ 成员函数
Posted
技术标签:
【中文标题】使用 Gcc 在运行时导入 C++ 成员函数【英文标题】:Import C++ member function at run-time with Gcc 【发布时间】:2015-10-03 10:40:57 【问题描述】:问题
我目前正在开发一个插件库,其中应该不仅可以导入 C-Linkage 符号,还可以导入所有导入的东西。
到目前为止,它可以工作,但问题是 gcc 会调用成员函数。
如果我导出以下内容:
static member_function(Class* c)
c->method();
它工作正常,我可以访问班级成员。但如果我执行以下操作:
void (Class ::*p)() = import("Class::method");
(x.*p)();
我得到了正确的指针,也能够调用函数和传递的参数,但是 this 指针指向涅槃。我认为 gcc 是从堆栈的错误位置或类似位置获取它。
它与 MSVC 配合得很好。
我正在使用 mingw-w64 5.1。
有人知道错误可能是什么吗?
简单示例:
plugin.cpp
#include <iostream>
namespace space
class __declspec(dllexport) SomeExportThingy
int i = 42;
public:
virtual void __declspec(dllexport) Method(int*) const
using namespace std;
cout << "Calling Method" << endl;
cout << pi << endl;
cout << *pi << endl;
cout << this << endl;
cout << this->i << endl;
loader.cpp
namespace space
class SomeExportThingy
///dummy to have some data in the address
int dummy[20];
;
int main()
auto h = LoadLibrary("plugin.dll");
auto p = GetProcAddress(h, "_ZNK5space16SomeExportThingy6MethodEPi");
typedef void (space::SomeExportThingy::*mptr)(int*) const;
///used because posix passed void*
auto fp = *reinterpret_cast<mptr*>(&p);
space::SomeExportThingy st;
int value = 22;
cout << "ValueLoc: " << &value << endl;
cout << "StLoc: " << &st << endl;
(st.*fp)(&value);
结果
现在发生的情况是,函数被调用并且指向 pi 的指针被正确传递。但是,this 指针完全搞砸了。 再次:它与 MSVC 一起工作,它正确地获取了 this 指针,但 gcc 得到了 this 错误。 我不知道为什么会发生这种情况,并且从方法中删除虚拟也不会改变这一点。 我不知道是什么原因造成的,所以也许有人知道 ABI 在这里做了什么。
这是我得到的指针:
0x00400000 == GetModuleHandleA(NULL) 0x61840000 == GetModuleHandleA("plugin.dll") 0x0029fcc4 == _&st 0x00ddcd60 == 这个我无法找到值之间的任何关系
【问题讨论】:
不确定,但您是否尝试使用损坏的函数名称调用import()
?
可能是由于名称修改...一般用途是将独立函数标记为外部,这可以防止它名称修改...使用它来检索对象然后你可以访问它的成员函数...
仅将函数标记为 extern
不足以防止名称篡改,@basav;它必须标记为extern "C"
。
如果您尝试将 MSVC 编译的方法导入 GCC (g++) 编译的应用程序,那么您必须通过extern "C"
接口执行此操作。名称修饰算法在不同的 C++ 编译器之间故意不同,因为生成的代码可能具有不同的(未记录且相互不兼容的)内部结构。
Mingw(正确大写为 MinGW)5.1 是什么意思? MinGW.org 发布的唯一 MinGW-5.1(拥有 MinGW 商标,以任何大小写形式)是一个(现在是古董)基于 GCC-3.x 的集成包。我猜你不是那个意思,在这种情况下,这不是一个 MinGW 问题;可能是mingw-w64
,这是一个不同的产品,与 MinGW.org 没有任何关联(并且在技术上侵犯了 MinGW 商标)。请澄清,和/或适当地标记。
【参考方案1】:
这不适用于 GCC:
typedef void (space::SomeExportThingy::*mptr)(int*) const;
///used because posix passed void*
auto fp = *reinterpret_cast<mptr*>(&p);
指向成员的指针的大小是普通函数指针(或void*
)大小的两倍,因此您正在从仅包含一个字的内存位置读取两个字。第二个词(告诉编译器如何调整调用的this
指针)是垃圾,它只是在堆栈上p
之后发生的任何事情。
见https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html:
在 C++ 中,指向成员函数 (PMF) 的指针是使用各种类型的宽指针来实现的,以处理所有可能的调用机制; PMF 需要存储有关如何调整“this”指针的信息,
p
是一个void*
,所以它是堆栈上占用sizeof(void*)
字节的内存位置。
&p
是指向该内存位置的指针。
reinterpret_cast<mptr*>(&p)
是指向同一地址的2*sizeof(void*)
字节的指针。
*reinterpret_cast<mptr*>(&p)
从大小仅为sizeof(void*)
字节的内存位置读取2*sizeof(void*)
字节。
坏事发生了。
【讨论】:
你太棒了,先生!这就是问题所在,如果我将 nullptr 放入第二部分,它就可以工作(对于非虚拟调用)。【参考方案2】:对于 linux,动态函数加载的函数有:dlopen()、dlsym() 和 dlclose()。请参考:dlopen() man page。
考虑到 C++ 方法名称是“错位的”,并且它们在所有其他方法之前传递了一个不可见的“*this”参数。这两个问题一起使得在使用动态链接时尝试直接访问 C++ 对象并非易事。
我发现的最简单的解决方案是使用公开访问 C++ 对象实例的“C”函数。
其次,当要实例化的代码位于 .so 库对象中时,C++ 对象的内存管理并非易事,尽管引用代码来自用户的应用程序。
关于为什么很难避免指向 C++ 成员方法的指针的详细答案,请参考:ISO CPP Reference, Pointers to Methods。
/** File: MyClass.h **/
// Explicitly ensure 'MyClassLoaderFunc' is NOT name mangled.
extern 'C' MyClass* MyClassLoaderFunc(p1, p2 ,p3, etc );
extern 'C' MyClass* MyClassDestroyerFunc(MyClass* p);
// Create function pointer typedef named 'LoaderFuncPtr'
typedef MyClass*(MyClassLoaderFunc* LoaderFuncPtr)(p1,p2,p3,etc);
// Define MyClass
class MyClass
/** methods & members for the class go here **/
char dummy[25];
int method( const char *data);
;
/** File: MyClass.cpp **/
#include "MyClass.h"
MyClass* MyLoaderFunc(p1, p2 ,p3, etc)
MyClass* newInstance = new MyClass::CreateInstance( p1, p2, p3, etc);
/** Do something with newInstance **/
return newInstance;
MyClass::method(const char* data)
/** File: MyProgram.cpp **/
#include "MyClass.h"
main()
// Dynamically load in the library containing the object's code.
void *myClassLibrary = dlopen("path/to/MyClass.so",RTLD_LOCAL);
// Dynamically resolve the unmangled 'C' function name that
// provides the bootstrap access to the MyClass*
LoaderFuncPtr loaderPtr = dlsym(myClassLibrary,"MyClassLoaderFunc");
DestroyFuncPtr destroyerPtr = dlsym(myClassLibrary,"MyClassDestroyerFunc");
// Use dynamic function to retrieve an instance of MyClass.
MyClass* myClassPtr = loadPtr(p1,p2,p3,etc);
// Do something with MyClass
myClassPtr->method();
// Cleanup of object should happen within original .cpp file
destroyPtr(myClassPtr);
myClassPtr = NULL;
// Release resources
dlclose(myClassLibrary);
return 0;
希望这会有所帮助..
我还建议将工厂范例作为更强大的解决方案,我将留给读者去探索。
【讨论】:
您能否从MyClassLoaderFunc()
返回一个std::unique_ptr<MyClass>
(并避免使用MyClassDestroyerFunc()
)或者这是不允许/可能/支持/可移植的extern "C"
链接?【参考方案3】:
正如乔纳森所指出的,指向成员的指针比普通的函数指针要大。 最简单的解决方案是保留并初始化额外的空间。
typedef void (space::SomeExportThingy::*mptr)(int*) const;
union
mptr fp;
struct
FARPROC function;
size_t offset;
;
combFp;
combFp.function = p;
combFp.offset = 0;
auto fp = combFp.fp;
【讨论】:
以上是关于使用 Gcc 在运行时导入 C++ 成员函数的主要内容,如果未能解决你的问题,请参考以下文章