如何调用通过使用 dlopen 加载共享对象 运行 时间创建的对象的方法

How to call methods of an object that is created by loading shared object run time using dlopen

我创建了一个名为 libmathClass.so 的测试库,我将从下面的代码中加载它。这个共享对象有一个 class 并且库调用被创建到这个 class 的 return 对象。 如何从下面显示的主要代码中调用此对象的方法。我从 ld(linker) 收到未定义的引用错误,因为它不知道方法的定义。

void* handle;
handle=dlopen("math1/libmathClass.so", RTLD_LAZY);
if(!handle)
{
    cout<<"error loading library: "<<dlerror()<<endl;
    exit(2);
}
else
{
    cout<<"***libmathClass.so library load successful!"<<endl;
}

void* (*mathInit) ();
mathInit = (void* (*)())dlsym(handle, "CreateMathOperationInstance");
if(!mathInit)
{
    cout<<"error loading instance method: "<<dlerror()<<endl;
    exit(3);
}
else
{
    cout<<"***method load successful!"<<endl;
}

mathOperationClass *mathInstance;
auto obj = (*mathInit)();
if(!obj)
{
    cout<<"object is not created"<<endl;
    exit(4);
}
else
{
    cout<<"object created!!!"<<endl;
    mathInstance = reinterpret_cast<mathOperationClass *>(obj);
}


int num1 = atoi(argv[1]);
int num2 = atoi(argv[2]);
cout<< mathInstance->AddInt(num1, num2)<<endl;

我用来编译的命令 - g++ --std=c++11 -g -o dynamicTest dynamicMain.cpp -ldl

错误信息: dynamicMain.cpp:54: 未定义对“mathOperationClass::AddInt(int, int)”的引用 collect2:错误:ld returned 1 退出状态

mathInit = (void* (*)())dlsym(handle, "CreateMathOperationInstance");

您在这里使用 dlsym() 在共享库中查找此符号。这必须是 C linkage 的函数,因为符号名称没有被破坏。这很重要,当您盯着这一行时请记住这一点:

cout<< mathInstance->AddInt(num1, num2)<<endl;

这里,AddIntmathInstance指向的class的一个方法。 class 方法只是另一个函数,只是它总是将隐藏的 this 指针作为额外参数。简而言之,这就是 class 方法,而典型的 C++ 实现实际上就是这种情况。 C++ 在技术上实际上并不需要这种情况。 C++ 实现可以自由地以任何方式实现方法,从而产生符合 C++ 规范的结果。但是,实际上,在典型的 C++ 实现中,这就是 class 方法的实际含义。具有引用为 this.

的额外参数的函数

因此,从某种意义上说,上面这行基本上等同于:

cout<< mathOperationClass::AddInt(mathInstance, num1, num2)<<endl;

这基本上就是这里发生的事情,说得非常松散。

这个 mathOperationClass::AddInt method/function 大概在您 dlopen 编辑的同一个共享库中;并且因为你 dlopen-ed 它而你实际上并没有 link 它,你有一个对该符号的引用,并且这个引用无法在运行时解析,因此你的运行时未定义符号错误。

如果您可以调用此 class 方法,唯一的方法就是同时使用 dlsym()。但是,为了能够真正实现这一目标,即使是最轻微的祈祷,也需要恰到好处地发生一大堆事情。

首先,你得搞清楚actual mangled C++ symbol name。使用我的 Linux x86_64 g++ 编译器作为参考,此方法的损坏名称将是“_ZN18mathOperationClass6AddIntEii”。有了它,您可以使用 dlsym 在您的共享库中找到这个符号(或者您的 C++ 实现的这个方法的实际损坏的符号名称是什么)。

一旦你有了这个符号,现在怎么办?好吧,让我们希望你的 C++ 实现确实有一个可破解的 C++ ABI,你可以在其中通过显式传递一个额外的 this 参数来调用 class 方法,如下所示:

int (*addInt)(mathOperationClass *, int, int)=
    reinterpret_cast<int (*)(mathOperationClass *, int, int)>
        (dlsym(handle, "_ZN18mathOperationClass6AddIntEii"));

cout << (*addInt)(mathInstance, num1, num2) << endl;

除非可以确认可以在您的 C++ 实现的 ABI 中以这种骇人听闻的方式调用 C++ 方法,否则整个纸牌屋都会崩溃。因为你已经在使用 dlopen() 你已经在非 portable 领域,使用你的 C++ 实现特定资源,所以你不妨弄清楚你的 C++ 方法是否可以这么叫的。如果没有,您将不得不弄清楚如何使用普通指针调用它们。

现在来点完全不同的东西...

综上所述:

有一种方法可以避免处理这种混乱情况:将此 class 方法设为虚拟 class 方法。虚拟 class 方法通过内部虚拟函数 table 分派。因此,只需尝试将此 AddInt 方法声明为虚拟 class 方法,然后按原样调用它。它很可能在您的 C++ 实现中起作用,因为在这种情况下,编译器不会发出 mathOperationClass::AddInt 的显式符号引用。它将通过虚拟函数 table 找到方法,该函数悄悄地附加到对象的每个实例。

当然,您还需要记住什么是虚函数,以及它们的含义。但是,在几乎所有情况下,这是调用从共享库动态加载的 classes 方法的一种非常便宜的方法。