4

I created a test library called libmathClass.so which I will be loading from the code below. This shared object has a class and library call is created to return object of this class. How can I call the methods of this object from the main code displayed below. I get undefined reference error from ld(linker) as it is not aware of the definition of methods.

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;

Command I used to compile - g++ --std=c++11 -g -o dynamicTest dynamicMain.cpp -ldl

Error message: dynamicMain.cpp:54: undefined reference to `mathOperationClass::AddInt(int, int)' collect2: error: ld returned 1 exit status

user2580634
  • 43
  • 1
  • 4
  • you need to link with the object that defines `AddInt()` of `mathInstance` – adrtam Dec 28 '18 at 02:33
  • Did you export your function name `CreateMathOperationInstance` as `extern "C"`? If not you need to supply the mangled versions of the name (not recommended). – Galik Dec 28 '18 at 02:54
  • I did export CreateMathOperationInstance as extern "C" which just calls a static method mathOperationClass::CreateInstance () { return new mathOperationClass (); } and returns the pointer of object. – user2580634 Dec 28 '18 at 06:44
  • Just a quick update. Its obvious that linker does not know definitions of methods called as they are loaded in run-time. The original intent of this question was to see if there is any other way to make this work(calling object methods defined in shared library). This particular solution works if the methods I am calling in the above program are defined as virtual methods which then will have its own vtable to play with and linker does not complain about undefined reference as they are sorted over runtime. virtual table a bliss for C++ :) – user2580634 Jan 02 '19 at 17:05

1 Answers1

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

Here you are using dlsym() to find this symbol in the shared library. This must be a function with C linkage, since the symbol name is not mangled. This is important, and keep this in mind, while you're staring at this line:

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

Here, AddInt is a method of the class pointed to by mathInstance. A class method is just another function, except that it always takes a hidden this pointer as an extra argument. That's what a class method is, in so many words, and this actually turns out to be the case with a typical C++ implementation. C++ technically does not actually require this to be the case. A C++ implementation is free to implement methods in whatever manner produces the results that are compliant with the C++ specification. But, in practical terms, in a typical C++ implementation this is what a class method actually is. A function with an extra parameter that's referenced as this.

Therefore, in a manner of speaking, the above line is basically equivalent to:

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

This basically is what's going on here, speaking very loosely.

This mathOperationClass::AddInt method/function is, presumably, in the same shared library that you dlopen-ed; and because you dlopen-ed it and you did not actually link to it, you have a reference to this symbol, and this reference cannot be resolved at runtime, hence your runtime undefined symbol error.

The only way you can even the slightest hope of invoking this class method -- if it can be invoked at all in this manner -- is by also using dlsym(). But in order to have even the slightest prayer of actually being be able to pull this off, a whole bunch of things need to happen just right.

First, you have to figure out the actual mangled C++ symbol name. Using my Linux x86_64 g++ compiler as a reference, the mangled name for this method would be "_ZN18mathOperationClass6AddIntEii". With that in hand, you can use dlsym to find this symbol (or whatever your C++ implementation's actual mangled symbol name for this method is) in your shared library.

And once you have this symbol, what now? Well, let's hope that your C++ implementation does, indeed, have a hackable C++ ABI where you can invoke a class method by explicitly passing it an extra this parameter, something like this:

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

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

This entire house of cards will collapse unless it can be confirmed that C++ methods can be invoked this hackish way, in your C++ implementation's ABI. Since you're already using dlopen() you're already in non-portable territory, using your C++ implementation-specific resources, so you might as well and figure out whether your C++ methods can be called this way. If not, you'll have to figure out how they can be called, using a plain pointer.

And now for something completely different...

Having said all of the above:

There is one way you can likely avoid dealing with this mess: by making this class method a virtual class method. Virtual class methods are dispatched via an internal virtual function table. So, just try declaring this AddInt method as a virtual class method, and calling it, as is. It's very likely to work in your C++ implementation, since the compiler will not emit, in this instance, an explicit symbol reference for mathOperationClass::AddInt. It will find the method via the virtual function table that's quietly attached to every instance of the object.

Of course, you also need to keep in mind what virtual functions are, and the implications of them. But, in nearly all cases this is a pretty cheap way to call methods of classes that are dynamically loaded from a shared library.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • `AddInt` is not a "normal pointer". It is a method call. And it is not invoked via the given pointer. This pointer is not a pointer to any function, but to an instance of a class. If you were to disassemble your compiled C++ code, you will discover that non-virtual method calls are equivalent to calls to external functions, using an external, mangled, symbol reference. That's how all modern C++ implementations work. – Sam Varshavchik Dec 28 '18 at 03:34
  • Except that this question is about code that is explicitly ***not*** linked to the shared library, but it is `dlopen`ed. – Sam Varshavchik Dec 28 '18 at 03:44
  • Ah yes. I was waiting for the 'cotcha' moment and this is it. My calls are going in the opposite direction (from the plugin to the shared library), but this situation is calling out to the plugin. Sorry, I am with you now. – Galik Dec 28 '18 at 03:46
  • AddInt is a regular public accessed method of class mathOperationClass. virtual method declaration seems like valid solution which I will try out. I actually tried linking the library during compilation and that worked fine. I have this issue only when dlopen'ing the shared library. – user2580634 Dec 28 '18 at 06:46
  • I upvoted for the suggestion to use virtual method which I did not think about. My methods were originally not virtual which is why linker was complaining about it. Thanks Sam Varshavchik for the suggestion, made me revisit my basics again. – user2580634 Jan 02 '19 at 17:09