Topic : Creating and using C Libraries
Author : LUPG
Page : << Previous 2  Next >>
Go to page :


compilation command.
Library File Creation - unlike a static library, a shared library is not an archive file. It has a format that is specific to the architecture for which it is being created. Thus, we need to use the compiler (either the compiler's driver, or its linker) to generate the library, and tell it that it should create a shared library, not a final program file.
This is done by using the '-G' flag with some compilers, or the '-shared' flag with other compilers.

Thus, the set of commands we will use to create a shared library, would be something like this:


cc -fPIC -c util_file.c
cc -fPIC -c util_net.c
cc -fPIC -c util_math.c
cc -shared libutil.so util_file.o util_net.o util_math.o


The first three commands compile the source files with the PIC option, so they will be suitable for use in a shared library (they may still be used in a program directly, even thought they were compiled with PIC). The last command asks the compiler to generate a shared library





Using A Shared "C" Library - Quirks And Solutions
Using a shared library is done in two steps:

Compile Time - here we need to tell the linker to scan the shared library while building the executable program, so it will be convinced that no symbols are missing. It will not really take the object files from the shared library and insert them into the program.
Run Time - when we run the program, we need to tell the system's dynamic loader (the process in charge of automatically loading and linking shared libraries into the running process) where to find our shared library.

The compilation part is easy. It is done almost the same as when linking with static libraries:

cc main.o -L. -lutil -o prog

The linker will look for the file 'libutil.so' (-lutil) in the current directory (-L.), and link it to the program, but will not place its object files inside the resulting executable file, 'prog'.

The run-time part is a little trickier. Normally, the system's dynamic loader looks for shared libraries in some system specified directories (such as /lib, /usr/lib, /usr/X11/lib and so on). When we build a new shared library that is not part of the system, we can use the 'LD_LIBRARY_PATH' environment variable to tell the dynamic loader to look in other directories. The way to do that depends on the type of shell we use ('tcsh' and 'csh', versus 'sh', 'bash', 'ksh' and similar shells), as well as on whether or not 'LD_LIBRARY_PATH' is already defined. To check if you have this variable defined, try:

echo $LD_LIBRARY_PATH

If you get a message such as 'LD_LIBRARY_PATH: Undefined variable.', then it is not defined.

Here is how to define this variable, in all four cases:

'tcsh' or 'csh', LD_LIBRARY_PATH is not defined:


    setenv LD_LIBRARY_PATH /full/path/to/library/directory
    


'tcsh' or 'csh', LD_LIBRARY_PATH already defined:


    setenv LD_LIBRARY_PATH /full/path/to/library/directory:${LD_LIBRARY_PATH}
    


'sh', 'bash' and similar, LD_LIBRARY_PATH is not defined:


    LD_LIBRARY_PATH=/full/path/to/library/directory
    export LD_LIBRARY_PATH
    


'sh', 'bash' and similar, LD_LIBRARY_PATH already defined:


    LD_LIBRARY_PATH=/full/path/to/library/directory:${LD_LIBRARY_PATH}
    export LD_LIBRARY_PATH
    



After you've defined LD_LIBRARY_PATH, you can check if the system locates the library properly for a given program linked with this library:

ldd prog

You will get a few lines, each listing a library name on the left, and a full path to the library on the right. If a library is not found in any of the system default directories, or the directories mentioned in 'LD_LIBRARY_PATH', you will get a 'library not found' message. In such a case, verify that you properly defined the path to the directory inside 'LD_LIBRARY_PATH', and fix it, if necessary. If all goes well, you can run your program now like running any other program, and see it role...

For an example of a program that uses a shared library, try looking at our shared library example directory.





Using A Shared "C" Library Dynamically - Programming Interface
One of the less-commonly used feature of shared libraries is the ability to link them to a process anytime during its life. The linking method we showed earlier makes the shared library automatically loaded by the dynamic loader of the system. Yet, it is possible to make a linking operation at any other time, using the 'dl' library. This library provides us with a means to load a shared library, reference any of its symbols, call any of its functions, and finally detach it from the process when no longer needed.

Here is a scenario where this might be appealing: suppose that we wrote an application that needs to be able to read files created by different word processors. Normally, our program might need to be able to read tens of different file formats, but in a single run, it is likely that only one or two such document formats will be needed. We could write one shared library for each such format, all having the same interface (readfile and writefile for example), and one piece of code that determines the file format. Thus, when our program is asked to open such a file, it will first determine its format, then load the relevant shared library that can read and translate that format, and call its readfile function to read the document. We might have tens of such libraries, but only one of them will be placed in memory at any given time, making our application use less system resources. It will also allow us to ship the application with a small set of supported file formats, and add new file formats without the need to replace the whole application, by simply sending the client an additional set of shared libraries.





Loading A Shared Library Using dlopen()
In order to open and load the shared library, one should use the dlopen() function. It is used this way:


#include <dlfcn.h>      /* defines dlopen(), etc.       */
.
.
void* lib_handle;       /* handle of the opened library */

lib_handle = dlopen("/full/path/to/library", RTLD_LAZY);
if (!lib_handle) {
    fprintf(stderr, "Error during dlopen(): %s\n", dlerror());
    exit(1);
}



The dlopen() function gets two parameters. One is the full path to the shared library. The other is a flag defining whether all symbols refered to by the library need to be checked immediatly, or only when used. In our case, we may use the lazy approach (RTLD_LAZY) of checking only when used. The function returns a pointer to the loaded library, that may later be used to reference symbols in the library. It will return NULL in case an error occured. In that case, we may use the dlerror() function to print out a human-readable error message, as we did here.





Calling Functions Dynamically Using dlsym()
After we have a handle to a loaded shared library, we can find symbols in it, of both functions and variables. We need to define their types properly, and we need to make sure we made no mistakes. The compiler won't be able to check those declarations, so we should be extra carefull when typing them. Here is how to find the address of a function named 'readfile' that gets one string parameter, and returns a pointer to a 'struct local_file' structure:





/* first define a function pointer variable to hold the function's address */
struct local_file* (*readfile)(const char* file_path);
/* then define a pointer to a possible error string */
const char* error_msg;
/* finally, define a pointer to the returned file */
struct local_file* a_file;

/* now locate the 'readfile' function in the library */
readfile = dlsym(lib_handle, "readfile");

/* check that no error occured */
error_msg = dlerror();
if (error_msg) {
    fprintf(stderr, "Error locating 'readfile' - %s\n", error_msg);
    exit(1);
}

/* finally, call the function, with a given file path */
a_file = (*readfile)("hello.txt");






As you can see, errors might occur anywhere along the code, so we should be carefull to make extensive error checking. Surely, you'll also check that 'a_file' is not NULL, after you call your function.




Unloading A Shared Library Using dlclose()
The final step is to close down the library, to free the memory it occupies. This should only be done if we are not intending to use it soon. If we do - it is better to leave it open, since library loading takes time. To close down the library, we use something like this:

dlclose(lib_handle);

This will free down all resources taken by the library (in particular, the memory its executable code takes up).




Automatic Startup And Cleanup Functions
Finally, the dynamic loading library gives us the option of defining two special functions in each library, namely _init and _fini. The _init function, if found, is invoked automatically when the library is opened, and before dlopen() returns. It may be used to invoke some startup code needed to initialize data structures used by the library,

Page : << Previous 2  Next >>