Loading Library Files in C++

Loading...

This article demonstrates how to load shared or dynamic library files in programs written in C++, which is not as straightforward as in C.

Device drivers and library files have always been associated with the C programming language, and dominated by C programmers, because of the straightforward symbols of C’s libraries, which are directly related to the function name. Loading a library in C is simpler than in C++, mainly due to the issue of name mangling, which we will examine later. Another problem of using dlopen in C++ is that the dlopen API supports loading of functions, but in C++, to use the methods of a class, normally, you need to instantiate it.

Name mangling

In any executable or shared library, all non-static functions are represented by a symbol, which is usually the same as the function name, and represents the start address of the function in memory. In C, the symbol is the same as the function name — e.g., the symbol for the init function will be init, since no two functions can have the same name.

However, in C++, because of overloading and polymorphism in classes, it is possible to have the same name for two functions in a program. Hence, it’s not possible to use the function name as the symbol. To solve this problem of having two functions with the same name, C++ compilers use name-mangling techniques, in which they change the symbol names (you can read more about this on Wikipedia). Name mangling makes it difficult for programmers to access a specific symbol in the compiled shared library file, even if they know the original function name.

The solution to this issue is to use the special keyword extern “C” before the function implementation (i.e., extern "C" void function_name()). This tells the C++ compiler to compile the function in C style — to keep the symbol name the same as the function name. We can use this keyword to define a function that returns an instance of a class, which also solves the must-instantiate-class problem mentioned earlier.

Now, let’s look at an example of building a library, and then loading it. First, let’s make the interface for the shared library, which we can use to reference the shared library and our main programs:

#ifndef TESTVIR H
#define TESTVIR_H

class TestVir
{
public:
  virtual void init()=0;
};

#endif

Let’s start by making a sample shared library, testLib.h:

#ifndef TESTLIB_H
#define TESTLIB_H

class TestLib
{
 public:
     void init();
};

#endif

In the above header file, we declared a class and the init method as public, as we need to access it later. The next code is testLib.cpp:

#include <iostream>
#include "testVir.h"
#include "testLib.h"

using namespace std;
void TestLib::init()
{
   cout<<"TestLib::init: Hello World!! "<<endl ;
}

//Define functions with C symbols (create/destroy TestLib instance).
extern "C" TestLib* create()
{
    return new TestLib;
}
extern "C" void destroy(TestLib* Tl)
{
   delete Tl ;
}

Our two-class helper functions with extern "C" and will be used to create and destroy an instance of the shared library class, and serve as access points (entry and exit points) of the library.

Let us compile and link the above class with the shared and fPIC options to g++:

g++ -shared -fPIC testLib.cpp -o testLib.so

Now, let’s write a program to access this library — main.cpp:

#include<iostream>
#include<dlfcn.h>
#include "testVir.h"

using namespace std;

int main()
{
    void *handle;
    handle = dlopen("./testLib.so", RTLD_NOW);
    if (!handle)
    {
           printf("The error is %s", dlerror());
    }

    typedef TestVir* create_t();
    typedef void destroy_t(TestVir*);

    create_t* creat=(create_t*)dlsym(handle,"create");
    destroy_t* destroy=(destroy_t*)dlsym(handle,"destroy");
    if (!creat)
    {
           cout<<"The error is %s"<<dlerror();
    }
    if (!destroy)
    {
           cout<<"The error is %s"<<dlerror();
    }
    TestVir* tst = creat();
    tst->init();
    destroy(tst);
    return 0 ;
}

In the above program, we used dlopen to load the library, then retrieved references to the symbols for our two access functions with dlsym, and then via these, first invoked the create function to get an instance of the shared object, invoked its init method, and then destroyed the object used to invoke the methods of the C++ class.

Next, compile and link the program with g++ -ldl main.cpp -o test. The option -ldl is used to link it with libdl.so to use the methods in the dlfcn header file.

The above scenario is basically used to build a plugin framework, where the plugins are dynamically loaded into the main component.

  • Leandro Arias Kapanguero

    Vera Orduna

  • Vera Orduna

    The above scenario is basically used to build a plugin framework, where the plugins are dynamically loaded into the main component.

  • Vera Orduna

    Todo eso para hacer un plugin ._. compilar trauma trauma T_T

  • c3120c

    class TestLib :public TestVir

  • psibi

    Nice Article.

All published articles are released under Creative Commons Attribution-NonCommercial 3.0 Unported License, unless otherwise noted.
Open Source For You is powered by WordPress, which gladly sits on top of a CentOS-based LEMP stack.

Creative Commons License.