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

时间:2018-12-28 02:15:32

标签: c++ dynamic shared-objects

我创建了一个名为libmathClass.so的测试库,将从下面的代码中加载该库。该共享库具有一个类,并且创建了库调用以返回该类的对象。 如何从下面显示的主要代码中调用该对象的方法。我从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返回1退出状态

1 个答案:

答案 0 :(得分:1)

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

您在这里使用dlsym()在共享库中找到此符号。这必须是具有C链接的函数,因为符号名称没有被修饰。这很重要,当您盯着这条线时,请记住这一点:

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

在这里,AddIntmathInstance指向的类的方法。类方法只是另一个函数,除了它始终将隐藏的this指针作为附加参数。用这么多的话来说,这就是类方法的事实,而事实证明,典型的C ++实现就是这种情况。从技术上讲,C ++实际上并不需要这种情况。 C ++实现可以自由地以任何方式产生符合C ++规范的结果的方法。但是,实际上,在典型的C ++实现中,这实际上是一个类方法。具有附加参数的函数,其引用为this

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

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

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

mathOperationClass::AddInt方法/函数大概位于您dlopen编辑的同一共享库中;并且因为您dlopen编辑了它,但实际上并未链接到它,所以您有对该符号的引用,并且该引用无法在运行时解析,因此您的运行时未定义符号错误。

您甚至可以丝毫希望调用该类方法(如果可以完全以这种方式调用)的唯一方法是也使用dlsym()。但是,要想真正做到这一点,甚至要有一点点祈祷,就需要一整件事。

首先,您必须找出actual mangled C++ symbol name。使用我的Linux x86_64 g ++编译器作为参考,此方法的错误名称将为“ _ZN18mathOperationClass6AddIntEii”。有了它,您就可以使用dlsym在共享库中找到此符号(或此方法的C ++实现的实际错误符号名称)。

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

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

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

除非可以确认C ++方法可以在您的C ++实现的ABI中以这种怪异的方式调用,否则整个纸牌屋将崩溃。由于您已经在使用dlopen(),因此您已经在非便携式领域中,使用了C ++实现特定的资源,因此您可能还需要弄清楚是否可以用这种方式调用C ++方法。如果没有,则必须使用普通指针弄清楚如何调用它们。

现在换个完全不同的东西...

已说完上述所有内容

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

当然,您还需要牢记什么是虚函数及其含义。但是,在几乎所有情况下,这都是一种调用从共享库动态加载的类的方法的廉价方法。