This week, I will present the dl library and discuss dynamic loading
concepts. In essence, dynamic loading consists of opening a library,
searching for one or more symbols (which can refer to functions,
variables, arrays, etc...), using the retrieved symbols, and closing
the library. All of the functions and data types pertaining to dynamic
loading are declared in the header .
The dl Functions
The dlerror() function reports the most recent error that occurred with
any of the remaining dl functions. I will discuss the other dl
functions shortly. The dlerror() function's prototype is as follows:
const char * dlerror(void);
dlerror() returns a string describing the error that occurred. If no
error occurred, then it returns NULL. Remember that this function
clears the error after it returns, so you must store the result if you
intend to use it later.
The dlopen() function opens a library and returns a matching handle. A
library is a file of compiled code modules. Here is an example of the
void * dlopen(const char * filename, int flags)
Typically, you pass an absolute pathname (i.e., a file beginning with a
slash) as the filename but in this case, dlopen() doesn't need to
search for the library. However, if you pass a simple filename, then
the function looks for the library in the following places until it
locates the specified library file:
1) A set of directories specified in the LD_ELF_LIBRARY_PATH
environment variable. If this variable isn't defined, the
directories listed in LD_LIBRARY_PATH are searched.
2) The libraries listed in the /etc/ld.so.cache file.
dlopen() returns NULL if it fails to open the specified library.
Two types of symbol resolution exist: lazy and immediate. A lazy
resolution means that symbol resolution is performed on demand. Lazy
resolution is useful when you need to load a small portion of a huge
library efficiently. By contrast, immediate resolution resolves all the
library's unresolved symbols at once. Immediate resolution is useful
when the library is small and for debugging purposes.
dlopen()'s second argument indicates the resolution type: RTLD_LAZY and
RTLD_NOW, for lazy and immediate resolution, respectively. You may
combine either of these constants with the RTLD_GLOBAL to export
symbols to other modules.
For the symbol lookup process, use the dlsym() function:
void* dlsym(void * libhandle, char * symbol);
libhandle is a value returned from a previous dlopen() call. The second
argument represents the function or variable's name that you want to
retrieve. If the symbol was found, then dlsym() returns its address
(you have to explicitly cast the return value to the desired type).
Otherwise, dlsym() returns NULL. Note, however, that a NULL value can
also be a valid address of a symbol, an initialized pointer for
example. Therefore, if the sought-after symbol may have a NULL value,
you have to call dlerror() after calling dlsym() to check whether the
retrieval was successful.
Calling dlclose() closes a library. It has the following prototype:
void * dlclose(void * libhandle);
Next week, I will continue this discussion and provide code examples
showing how to load libraries dynamically.