使用 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*) 字节的内存位置。 &amp;p 是指向该内存位置的指针。 reinterpret_cast&lt;mptr*&gt;(&amp;p) 是指向同一地址的2*sizeof(void*) 字节的指针。 *reinterpret_cast&lt;mptr*&gt;(&amp;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&lt;MyClass&gt;(并避免使用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++ 成员函数的主要内容,如果未能解决你的问题,请参考以下文章

C++函数指针与成员函数指针

C++ 静态成员函数

C++ 静态成员函数

C++ 静态成员函数

c++中静态成员变量和静态成员函数(笔试经历)

运行时在构造函数中初始化向量类成员——C++