使用 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 在这里做什么。

这是我得到的指示:

我找不到这些值之间的任何关系

这不适用于 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 去探索。