Mar 5, 2012

C++: Dynamic linking library code (linux)

This technique is useful for creating a plug-in architecture; create a header with a standard interfaces and functions that third-party developers can then overwrite. Your code will then load the shared library, create pointer references to the standard functions within the library and then execute the third-party code. I will probably write up another post that will display how this works :D

This was created using Code::Blocks; I dunno why, but I find this IDE easier to use than Eclipse or NetBeans for hobby coding.

First, lets create that header file that describes the standard interfaces and functions.

vo.h
// Ensure the programmer doesn't import our declarations twice.
// This also stops name conflicts from occurring early on.
#ifndef VO_H
#define VO_H

// Prevent the c++ compiler from corrupting names
// and creating "unresolved symbols" when linking
#ifdef __cplusplus
extern "C" {
#endif

// Standard initiliaser
int init( void );

#ifdef __cplusplus
}
#endif

#endif
Just to help us visualise what is happening, let's create the implementation of our header. Nothing too complex... all we will do is return the internal state of a global variable (yes this is not good practice, but it's just for show).

vo.cpp

// Import local libraries
#include "voNameGen.h"

// use the std namespace as the default
using namespace std;

bool bInit = false;

int init( void )
{
return bInit;
}
Now we can compile our shared library. Code::Blocks allowed me to create a project with settings to automatically do this for me, but for those using something different this is the command to run (make sure you do so inside the directory that stores the source code!!):

g++ -fPIC -shared vo.cpp -o libVo.so

So now we have our shared library object. Let's have a quick look inside with nm. This tool shows the symbols inside our library, which are used by other programs to 'hook' into our code. After running the command, you will notice our function, init, is at the bottom of the list. This is the 'hook' we are going to use...

nm -d libVo.so

Now let's use our library!!

testrig.c

// Libraries for input and output
#include <stdio.h>
#include <iostream>

// Library for dynamic linking
#include <dlfcn.h>

// Make sure this points to our
// standard header file!!!!
#include "vo.h"

// Use the standard namespace
// Change only if you know what
// you are doing!!
using namespace std;

int main()
{
    // This will hold our reference
    // to our library...
    void *lib_handle;

   // This is our prototype function
   // for init... note the asterixes
   // (Learn about pointers before
   // changing!!!)
   int* (*init)();

   // Hold out error code
   char *error;

   // Using iostream to create output
   cout << "Hello world!" << endl;

   // Opening up our shared library
   lib_handle = dlopen("libVo.so", RTLD_LAZY);

   // If the library failed to open, exit
   if (!lib_handle)
   {
      // This uses stdlib to create output
      fprintf(stderr, "%s\n", dlerror());
      return 1;
   }

   // Point our function to the address of our desired
   // function located in the library....
   init = (int* (*)())dlsym(lib_handle, "init");

   // If we failed to find the function, exit
   if ((error = dlerror()) != NULL)
   {
      // This uses stdlib to create output
      fprintf(stderr, "%s\n", error);
      return 1;
   }

   // Execute the function in the library
   // and pipe the output directly to the
   // command line
   cout << fn() << endl;

   // Close our library and save memory
   dlclose(lib_handle);

   // Exit the program
   return 0;
}
There is nothing more I can really add to this; just go through the code and read the comments! To compile this code manually just type in (the -ldl flag is important as it tells the compiler to use the dlfcn library):

g++ -ldl main.cpp -o main

UPDATE: It has been noticed in the comments that "g++ main.cpp -ldl -o main" works instead of "g++ -ldl main.cpp -o main". Keep this in mind in case you come across any errors.

Now just make sure libVo.so is in the same directory as main before you execute it!

References

A Stack Overflow discussion on the topic
The YoLinux tutorial on the topic of libraries

3 comments:

  1. what might the cause of this be?

    testrig.c:(.text+0x3d): undefined reference to `dlopen'
    testrig.c:(.text+0x4d): undefined reference to `dlerror'
    testrig.c:(.text+0x82): undefined reference to `dlsym'
    testrig.c:(.text+0x8b): undefined reference to `dlerror'
    testrig.c:(.text+0xcb): undefined reference to `dlclose'

    thankyou
    tobias

    ReplyDelete
    Replies
    1. seems "g++ main.cpp -ldl -o main" instead of "g++ -ldl main.cpp -o main" did the trick. thanks for the tutorial man

      Delete
    2. no problems... I have updated the tutorial to note this.

      I also have noticed that some of my code got jumbled up since I have updated the layout. I am surprised you managed to make sense of it! Should be fixed now...

      Delete

Thanks for contributing!! Try to keep on topic and please avoid flame wars!!