Breaking Monolithic Applications
As projects and applications become more sophisticated their code size also start to increase. This means that the size of the final executable would also be big. A big executable would be heavy on resources. However, in practical world it is observed that not every piece of code that has been written would be used by an user e.g. if we take an example of a word processing software, a user not always uses the functionality of printing the document. So it makes more sense to use the piece of executable code only when it is required and Dynamic libraries allow a programmer to do so. The code in a Dynamic library is not linked with the application but loaded at run-time when the request for that library is made by application.
While linking a library statically, the name of the library is provided to the compiler/linker at command line. This enables the compiler to resolve the symbols and generate error for undefined symbols at linking time. If there are any symbols which have been referenced but not defined, then the executable would not be formed.
Unlike static linking, in dynamic linking no previous information for the library is provided to the compiler or linker. Dynamic libraries are linked into the program in two stages. First, during compile time, the linker verifies that all the symbols referenced in the program, are either linked into the program, or in one of its shared libraries. However, the object files from the dynamic library are not inserted into the executable file. Instead, when the program is started, a program in the system (called a dynamic loader) checks out which shared libraries were linked with the program, loads them to memory, and attaches them to the copy of the program in memory. Despite the stated disadvantages dynamically linked libraries have advantages as well:
1. Easy Up-gradation: If the functions inside the dynamic library change then the whole application does not have to be recompiled. This is very useful for applications which have to be changed constantly e.g. an Anti-Virus which has to keep updating itself for new methods of searching viruses and repairing the damage. The ani-virus can keep the searching techniques and repairing methods in a dynamic library and keep updating that library instead of updating the complete application.
2. Low resource requirements: The size of an executable using dynamic library is smaller than the one which is using a static library. This would make the application relatively light on resources than it would have been if all the code was statically linked.
3. Flexible applications: Consider a word processing application with the spell checking feature. If the application implements the spell checking feature in a dynamic library then the spell checker can not only be upgraded but third party vendors can also create spell checkers for this application. The user then would have variety of choices to choose from.
Creating Dynamic library
When a static library is created, linker knows beforehand the memory requirements of the library and therefore can manage the memory locations for the library. The linker can generate absolute addresses for the variables and jump instructions while working with static libraries. However, in case of dynamic libraries this is not possible since the libraries are loaded at run-time and the memory address where these dynamic libraries would be mapped at run-time cannot be determined at link-time. Therefore dynamics libraries have to be created as Position independent. Therefore, creation of dynamic libraries comprise of two steps :
1. Generating Position Independent Object Files
2. Combining the Object files into a shared library
Generating Position Independent Object file
Position independent code can be generated by specifying the -fPIC or -fpic option to the complier.
Two source files add.c and substract.c can be compiled into Position Independent code as following:
gcc -c -fPIC add.c -o add.o
gcc -c -fPIC substract.c -o substract.o
Combining the Object files into a shared library
Once the Position independent object files have been created, they can be combined into a shared object files by gcc itself by specifying the -shared (or -G) option. The following command would generate the shared library libmymath.so from add.o and substract.o.
gcc -shared libmymath.so add.o substract.o
Configuring the Dynamic Library
When a client requests for a Dynamic Library at Run time, the Dynamic loader looks for the library in some specific paths. These paths are specified in the environment variable LD_LIBRARY_PATH. Therefore the system can be configured to look for your dynamic library in the following two ways using LD_LIBRARY_PATH :
1. Place the library in the paths specified by the LD_LIBRARY_PATH. However, this would normally require superuser privileges since the paths already specified in the environment variable would be standard paths such as /usr/lib.
2. Add the path containing the library to the LD_LIBRARY_PATH. Assuming that the library is kep in the path "/user/guest/mylibs/", this can be done by using the command
Using a Dynamic Library
The DL library provides various APIs for interacting with a dynamic library. Using a dynamic library in a program usually involves three steps:
1. Loading the Shared library in the current program's context using dlopen
2. Calling the functions present in the dynamic library using dlsym
3. Unloading the library using dlclose
Loading the Shared library
The dlopen function is used to load the shared library/ The dlopen function takes two parameters. First parameter is the name of the library to be opened and second parameter decides whether symbols of the library referred in the program should be checked immediately, or only when used.
The dlopen function returns the handle to the library which can be used to access the symbols present in the library. The libmymath.so can be used in the following manner
libHandle = dlopen("libmath.so", RTLD_LAZY);
fprintf(stderr, "Error during dlopen(): %s\n", dlerror());
Calling the functions present in the dynamic library
After the handle has been obtained for the library, this handle can be used to access the symbols present inside the library using dlsym. This funtion has two parameters. First parameter is the handle to the library and second parameter is the name of the symbol. dlsym returns the address for the requested symbols. This means that a pointer of same type as the symbol requested needs to be created for storing the address returned by dlsym.
In order to access the function void add( int a, int b) we need a pointer of this type which can be used to call this function from the library.
/* Define a function pointer for function add */
void (*add) (int a, int b);
int firstNumber = 10, secondNumber = 20, result;
readfile = dlsym(libHandle, "add");
error_msg = dlerror();
fprintf(stderr, "Error locating 'readfile' - %s\n", error_msg);
result = (*add)(firstNumber, secondNumber);
printf(" The addition of %d amd %d is %d \n",
firstNumber, secondNumber, result);
Unloading the dynamic library
Ideally the dynamic library should be unloaded as soon as possible so as to free up the resources it has been holding. This can be done through the function dlclose. The dlclose function takes the handle of the library to be unloaded as the parameter.
However, if you need to access the library soon, then do not unload the library as loading the library again would take time.