使用 Gcc 在 运行 时间导入 C++ 成员函数
Import C++ member function at run-time with Gcc
问题
我目前正在开发一个插件库,其中应该不仅可以导入 C-Linkage 符号,还可以导入所有导入的东西。
到目前为止它可以工作,但问题是 gcc 会破坏成员函数调用。
如果我导出以下内容:
static member_function(Class* c)
{ c->method();}
它工作正常,我可以访问 class-成员。但是,如果我执行以下操作:
void (Class ::*p)() = import("Class::method");
(x.*p)();
我得到了正确的指针,也能够调用函数和传递的参数,但是 this 指针指向 nirvana。我认为 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 的指针被正确传递。但是,这个指针完全搞砸了。
同样:它与 MSVC 一起工作,MSVC 正确获取了 this 指针,但是 gcc 获取了 this 错误。
我不知道为什么会这样,从方法中删除虚拟也不会改变。
我不知道是什么原因造成的,所以也许有人知道 ABI 在这里做什么。
这是我得到的指示:
- 0x00400000 == GetModuleHandleA(NULL)
- 0x61840000 == GetModuleHandleA("plugin.dll")
- 0x0029fcc4 == _&st
- 0x00ddcd60 == 这个
我找不到这些值之间的任何关系
这不适用于 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:
In C++, pointer to member functions (PMFs) are implemented using a wide pointer of sorts to handle all the possible call mechanisms; the PMF needs to store information about how to adjust the ‘this’ pointer,
p
是一个 void*
所以它是堆栈上的一个内存位置,占用 sizeof(void*)
字节。
&p
是指向该内存位置的指针。
reinterpret_cast<mptr*>(&p)
是指向同一地址的 2*sizeof(void*)
字节的指针。
*reinterpret_cast<mptr*>(&p)
从大小仅为 sizeof(void*)
字节的内存位置读取 2*sizeof(void*)
字节。
- 坏事发生。
正如 Jonathan 所指出的,指向成员的指针比普通函数指针更大。
最简单的解决方案是保留并初始化额外的 space.
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;
对于linux,动态函数加载的函数有:dlopen()、dlsym()、dlclose()。请参考:dlopen() man page.
考虑到 C++ 方法名称是 'mangled' 并且它们有一个不可见的“*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;
}
希望这对您有所帮助..
我还建议将工厂模式作为更强大的解决方案,我将留给 reader 去探索。
问题
我目前正在开发一个插件库,其中应该不仅可以导入 C-Linkage 符号,还可以导入所有导入的东西。
到目前为止它可以工作,但问题是 gcc 会破坏成员函数调用。
如果我导出以下内容:
static member_function(Class* c)
{ c->method();}
它工作正常,我可以访问 class-成员。但是,如果我执行以下操作:
void (Class ::*p)() = import("Class::method");
(x.*p)();
我得到了正确的指针,也能够调用函数和传递的参数,但是 this 指针指向 nirvana。我认为 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 的指针被正确传递。但是,这个指针完全搞砸了。 同样:它与 MSVC 一起工作,MSVC 正确获取了 this 指针,但是 gcc 获取了 this 错误。 我不知道为什么会这样,从方法中删除虚拟也不会改变。 我不知道是什么原因造成的,所以也许有人知道 ABI 在这里做什么。
这是我得到的指示:
- 0x00400000 == GetModuleHandleA(NULL)
- 0x61840000 == GetModuleHandleA("plugin.dll")
- 0x0029fcc4 == _&st
- 0x00ddcd60 == 这个
我找不到这些值之间的任何关系
这不适用于 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:
In C++, pointer to member functions (PMFs) are implemented using a wide pointer of sorts to handle all the possible call mechanisms; the PMF needs to store information about how to adjust the ‘this’ pointer,
p
是一个void*
所以它是堆栈上的一个内存位置,占用sizeof(void*)
字节。&p
是指向该内存位置的指针。reinterpret_cast<mptr*>(&p)
是指向同一地址的2*sizeof(void*)
字节的指针。*reinterpret_cast<mptr*>(&p)
从大小仅为sizeof(void*)
字节的内存位置读取2*sizeof(void*)
字节。- 坏事发生。
正如 Jonathan 所指出的,指向成员的指针比普通函数指针更大。
最简单的解决方案是保留并初始化额外的 space.
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;
对于linux,动态函数加载的函数有:dlopen()、dlsym()、dlclose()。请参考:dlopen() man page.
考虑到 C++ 方法名称是 'mangled' 并且它们有一个不可见的“*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;
}
希望这对您有所帮助..
我还建议将工厂模式作为更强大的解决方案,我将留给 reader 去探索。