C/C++ library programming on Linux - Part three: Dynamic libraries using POSIX API

This is my third article in the “C/C++ library programming on Linux” series. I strongly suggest you read the first two parts parts of this article series where I’ve given some background about libraries on Linux operating system and explained static and dynamic libraries.

C/C++ library programming on Linux – Part one: Static libraries

C/C++ library programming on Linux – Part two: Dynamic libraries

In this third article I will explain the most interesting way of reusing code using libraries on Linux operating system - by using POSIX ("Portable Operating System Interface for Unix") application programming interface. Using POSIX functions dlopen(), dlsym(), dlclose() and dlerror() you can load and unload your shared libraries during the course of your programs operation. This functions present interface to the Linux dynamic linking loader explained in the first part of my "C/C++ library programming on Linux" article series. This system calls are typically used for implementing stuff like plugins for your application so that you can load functionality provided inside plugin specific dynamic library on-demand. So here's simple example of loading dynamic library into the "cprog" program presented inside my first article "C/C++ library programming on Linux – Part one: Static libraries".

To implement this POSIX functions we must modify source code of our "cprog" program presented in the first part of this article series. Library source code files "ctest1.c" and "ctest2.c" remain the same.

We must take into account that C++ does it (in)famous name mangling so besides this new "cprog.c" source code, we need to instruct GCC to leave our library function names intact. That's why we need to define "ctest.h" library header file with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef CTEST_H
#define CTEST_H
 
#ifdef __cplusplus
extern "C" {
#endif
 
void (*ctest1)(int *);
void (*ctest2)(int *);
 
#ifdef __cplusplus
}
#endif
 
#endif

By including the "ctest.h" header file into our "cprog.c" source code, names of the ctest1() and ctest2() functions symbols wouldn't be mangled and our on demand linking will succeed. Now here's the new "cprog.c" source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include "ctest.h"
 
int main(){
    void *handle;
    char *error;
    int x, y, z;
 
    handle = dlopen ("libctest.so.1", RTLD_LAZY);
    if (!handle) {
        fputs (dlerror(), stderr);
        exit(1);
    }
 
    ctest1 = dlsym(handle, "ctest1");
    if (( error = dlerror() ) != NULL)  {
        fputs(error, stderr);
        exit(1);
    }
 
    ctest2 = dlsym(handle, "ctest2");
    if (( error = dlerror() ) != NULL)  {
        fputs(error, stderr);
        exit(1);
    }
 
    ctest1(&x);
    ctest2(&y);
    z = (x / y);
    printf("%d / %d = %d\n", x, y, z);
    dlclose(handle);
    return 0;
}

Now lets explain this new code. First two lines include standard C header files and that isn't anything special. The third line includes "dlfcn.h" with the necessary macros like RTLD_LAZY (man dlopen and man dlfcn.h for detailes) and the fourth line includes our "ctest.h" header file to prevent C++ from mangling our function symbol names. After obtaining handle to our "libctest.so.1" library using dlopen() we must obtain address of our library functions by using dlsym() and store pointers to those functions inside ctest1() and ctest2() function pointers declared in the "ctest.h" header file. Calling dlsym() would fail if linker doesn't find symbol names for our ctest1() and ctest2() functions (if those functions definitions doesn't exists inside the "libctest.so.1" dynamic library) or if we let C++ to mangle symbol names. At this point we can use our functions to find solution to the 100/5 math problem. After we finish using those functions we should call dlclose() with the parameter of handle to our library. For more info about this system calls consult Linux built-in user manual using man command.

Here's the code for compilation of our "ctest" library and "cprog" program:

1
2
3
4
5
6
7
gcc -Wall -fPIC -c ctest1.c ctest2.c
gcc -shared -Wl,-soname,libctest.so.1 -o libctest.so.1.0 ctest1.o ctest2.o
ln -sf libctest.so.1.0 libctest.so
ln -sf libctest.so.1.0 libctest.so.1
gcc -Wall -o cprog cprog.c -ldl
export LD_LIBRARY_PATH=.
./cprog

If we compare this compilation code with the code given in part two of the "C/C++ library programming on Linux" article series we can see that the only thing that's changed is line five where we were linking our program with the library. Now we don't need to give -lctest parameter because we are not performing runtime linking with "ctest" library. Instead of that we perform on-demand linking with this library during our programs operation. And once again the result of 100/5 operation equals 20 and this is the output of ./cprog command. This means that everything is once again in order.

This is the end of the last article of my "C/C++ library programming on Linux" article series. I've hoped you have enjoyed reading and studying as much as I've enjoyed writing about libraries on Linux operating system. Once again discussion is open for any questions or suggestions. I wish you all happy Linux programming!

DevGenii

A quality focused Magento specialized web development agency. Get in touch!

One thought on “C/C++ library programming on Linux - Part three: Dynamic libraries using POSIX API

  1. Teno

    Thank you Marko for making what would have been a complex task very simple. I suppose we should thank the Linux developers for creating such a simple way to generate dynamic libraries. This was one of the rare times where I was able to simply copy and paste line-for-line code examples and they actually worked with one minor exception. On the version of Linux I’m using the lines
    void ctest1(int *);
    void ctest2(int *);

    caused errors this error:
    cprog.cpp:17:36: warning: invalid conversion from ‘void*’ to ‘void (*)(int*)’ [-fpermissive]

    Fortunately the compiler supplied the answer and I affixed “-fpermissive” to this line:

    gcc -Wall -o cprog cprog.c -ldl -fpermissive

    The errors were turned to warnings and life goes on!

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *