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!
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!