In previous two parts we’ve dealt with creating dynamic libraries on MacOS. Today we’re moving on to different system – Linux. I was planning to go with Windows, but running a web browser while using Virtualbox on my poor laptop is a nightmare and I think I will need to run my old laptop for this.
Nonetheless, here we are. After some struggle with manual pages, g++ tricks 2-week idleness finishes. As always you can find code on gitlab. I tried to reproduce closely what I was trying to achieve in previous blog posts so that I will be able to write a summary comparing different approaches.
Library code and build process
Header file included 4 functions – constructor, destructor, add and sub:
extern "C" void init() __attribute__((constructor)); extern "C" void destroy() __attribute__((destructor)); extern "C" int add(int, int); extern "C" int sub(int, int);
Source file defined those functions:
#include <iostream> #include "mymath.h" void init() { printf("Initializing mymath\n"); } void destroy(){ printf("Destroying mymath\n"); } int add(int x, int y){ std::cout << "Adding\n"; return x + y; } int sub(int x, int y){ std::cout << "Subtracting\n"; return x - y; }
Now, something new is in Makefile:
all: g++ -Wall -fPIC -shared -Wl,-soname,libmymath -Wl,-init,init mymath.cpp -o libmymath.so.1.0 -lc
A lot of switches come here, so let’s break it down.
- -Wall – includes warnings
- -fPIC – informs compiler to generate “position independent code“, which is required by dynamic libraries. Quoting the manual: “such code accesses all constant addresses through a global offset table ( GOT ). The dynamic loader resolves the GOT entries when the program starts”.
- -shared – informs compiler to produce linkable object.
- -Wl – linker options giving my library a name by which i will be able to refer to it later (using
-lmymath
while compiling target program) and defining entry point (I did not define any “exit” points with-fini
to see what happens).
The guide I was using suggested splitting the creation of an object file and an ELF file, which I skipped.
Program with linked library
First, I wanted to try out how a library linked at compilation time works. Well… This was interesting. Let’s look at the Makefile:
all: g++ -Wall -Iinclude -Llib src/main.cpp -lmymath -o bin/test
I do not know how much time I wasted before I learned that I need to place -lmymath
after source files. Funny enough, in previous parts this was the default way Simple C++ plugin for VS Code generated a Makefile, but this time writing it from scratch I got stuck for ever. As they say: hours of debugging can save minutes of reading documentation. Everything else was pretty straightforward: I copied header file to include/
and library to lib/
folder and wrote a source file:
#include <cstdio> #include "mymath.h" int main(){ printf("Mymath adding. 3+4=%d\n", add(3, 4)); printf("Mymath subtracting 3-4=%d\n", sub(3, 4)); return 0; }
And finally the output:
Initializing mymath Initializing mymath Adding Mymath adding. 3+4=7 Subtracting Mymath subtracting 3-4=-1 Destroying mymath
Note that “Initializing mymath” appears twice and where the results of add
and sub
functions are evaluated. I am curious whether this double call to constructor is caused by passing -Wl,-init,init
to compiler as well as setting constructor
attribute in source code. I am keen to play around with it more and search something in documentation. If you know about it, drop me a line in comments.
With regards to evaluating my library functions before calling printf
– this is something I expected, but at first I was a little confused when I used cout
below.
Program with dynamically loaded library
Loading additional code on runtime turned out to be very similar as in linux: get handle to library, load functions, use them, unload. Both use the same POSIX functions.
#include <iostream> #include <dlfcn.h> int main() { void* lib_handle; int (*add)(int, int); int (*sub)(int, int); lib_handle = dlopen("/usr/local/lib/libmymath.so", RTLD_LAZY); if(!lib_handle) { std::cout << "Did not find libmymath\n"; return 1; } add = (int(*)(int, int))dlsym(lib_handle, "add"); sub = (int(*)(int, int))dlsym(lib_handle, "sub"); if(!add || !sub){ std::cerr << "Could not load functions\n"; return 2; } std::cout << "adding 3+4=" << (*add)(3, 4) << std::endl; std::cout << "subtracting 3-4=" << (*sub)(3, 4) << std::endl; dlclose(lib_handle); return 0; }
The output was different due to the fact that I used cout
instead of printf
and initializers seem to not have been called. I have no idea why.
adding 3+4=Adding 7 subtracting 3-4=Subtracting -1
Summary
That’s it. Same task, two different operating systems. Next week, I will try to deliver similar example for Windows and then, one week later a comparison of all the approaches and summary of what I’ve learned.
Check this yolinux post on libraries, since it contains more extensive description of what I was doing here.