Section 27.1. The dl Interface

   


27.1. The dl Interface

Dynamic loading consists of opening a library, looking up any number of symbols, handling any errors that occur, and closing the library. All the dynamic loading functions are declared in one header file, <dlfcn.h>, and are defined in libdl (link the application with -ldl to use the dynamic loading functionality).

The dlerror() function returns a string describing the most recent error that occurred in one of the other three dynamic loading functions:

 const char * dlerror (void); 


Each time it returns a value, it clears the error condition. Until another error condition is created, it continues to return NULL instead of a string. The reason for this unusual behavior is detailed in the description of the dlsym() function.

The dlopen() function opens a library. This involves finding the library file, opening the file, and doing some preliminary processing. Environment variables and the options passed to dlopen() determine the details:

 void * dlopen (const char *filename, int flag); 


If filename is an absolute path (that is, it begins with a / character), dlopen() does not need to search for the library. This is the usual way to use dlopen() from within application code. If filename is a simple file name, dlopen() searches for the filename library in these places:

  • A colon-separated set of directories specified in the environment variable LD_ELF_LIBRARY_PATH, or, if LD_ELF_LIBRARY_PATH does not exist, in LD_LIBRARY_PATH.

  • The libraries specified in the file /etc/ld.so.cache. That file is generated by the ldconfig program, which lists every library it finds in a directory listed in /etc/ld.so.conf at the time that it is run.

  • /usr/lib

  • /lib

If filename is NULL, dlopen() opens an instance of the current executable. This is useful only in rare cases. dlopen() returns NULL on failure.

Finding the files is the easy part of dlopen()'s job; resolving the symbols is more complex. There are two fundamentally different types of symbol resolution, immediate and lazy. Immediate resolution causes dlopen() to resolve all the unresolved symbols before it returns; lazy resolution means that symbol resolution occurs on demand.

If most of the symbols will end up being resolved in the end, it is more efficient to perform immediate resolution. However, for libraries with many unresolved symbols, the time spent resolving the symbols may be noticeable; if this significantly affects your user interface, you may prefer lazy resolution. The difference in overall efficiency is not significant.

While developing and debugging, you will almost always want to use immediate resolution. If your shared objects have unresolvable symbols, you want to know about it immediately, not when your program crashes in the middle of seemingly unrelated code. Lazy resolution will be a source of hard-to-reproduce bugs if you do not test your shared objects with immediate resolution first.

This is especially true if you have shared objects that depend on other shared objects to supply some of their symbols. If shared object A depends on a symbol b in shared object B, and B is loaded after A, lazy resolution of b will succeed if it happens after B is loaded, but it will fail before B is loaded. Developing your code with immediate resolution enabled will help you catch this type of bug before it causes problems.

This implies that you should always load modules in reverse order of their dependencies: If A depends on B for some of its symbols, you should load B before you load A, and you should unload A before you unload B. Fortunately, most applications of dynamically loaded shared objects have no such interdependencies.

By default, the symbols in a shared object are not exported and so will not be used to resolve symbols in other shared objects. They will be available only for you to look up and use, as will be described in the next section. However, you may choose to export all the symbols in a shared object to all other shared objects; they will be available to all subsequently loaded shared objects.

All of this is controlled by the flags argument. It must be set to RTLD_LAZY for lazy resolution or RTLD_NOW for immediate resolution. Either of these may be OR'ed with RTLD_GLOBAL in order to export its symbols to other modules.

If the shared object exports a routine named _init, that routine is run before dlopen() returns.

The dlopen() function returns a handle to the shared object it has opened. This is an opaque object handle that you should use only as an argument to subsequent dlsym() and dlclose() function calls. If a shared object is opened multiple times, dlopen() returns the same handle each time, and each call increments a reference count.

The dlsym() function looks up a symbol in a library:

 void * dlsym (void *handle, char *symbol); 


The handle must be a handle returned by dlopen(), and symbol is a NULL terminated string naming the symbol you wish to look up. dlsym() returns the address of the symbol that you specified, or it returns NULL if a fatal error occurs. In cases in which you know that NULL is not the correct address of the symbol (such as looking up the address of a function), you can test for errors by checking to see if it returned NULL. However, in the more general case, some symbols may be zero-valued and be equal to NULL. In those cases, you need to see if dlerror() returns an error. Since dlerror() returns an error only once and then reverts to returning NULL, you should use code like this:

 /* clear any error condition that has not been read yet */ dlerror(); p = dlsym(handle, "this_symbol"); if ((error = dlerror()) != NULL) {     /* error handling */ } 


Since dlsym() returns a void *, you need to use casts to make the C compiler stop complaining. When you store the pointer that dlsym() returns, store it in a variable of the type that you want to use, and make your cast when you call dlsym(). Do not store the result in a void * variable; you would have to cast it every time you use it.

The dlclose() function closes a library.

 void * dlclose (void *handle); 


dlclose() checks the reference count that was incremented on each duplicate dlopen() call, and if it is zero, it closes the library. This reference count allows libraries to use dlopen() and dlclose() on arbitrary objects without worrying that the code that called it has already opened any of those objects.

27.1.1. Example

In Chapter 8, we presented an example of using a normal shared library. The shared library that we built, libhello.so, can also be loaded at run time. The loadhello program loads libhello.so dynamically and calls the print_hello function it loads from the library.

Here is loadhello.c:

  1: /* loadhello.c */  2:  3: #include <dlfcn.h>  4: #include <stdio.h>  5: #include <stdlib.h>  6:  7: typedef void (*hello_function)(void);  8:  9: int main (void) { 10:    void *library; 11:    hello_function hello; 12:    const char *error; 13: 14:    library = dlopen("libhello.so", RTLD_LAZY); 15:    if (library == NULL) { 16:       fprintf(stderr, "Could not open libhello.so: %s\n", 17:               dlerror()); 18:       exit(1); 19:    } 20: 21:    /* while in this case we know that the print_hello symbol 22:     * should never be null, that is not true for looking up 23:     * arbitrary symbols. So we demonstrate checking dlerror()'s 24:     * return code instead of dlsym()'s. 25:     */ 26:    dlerror(); 27:    hello = dlsym(library, "print_hello"); 28:    error = dlerror(); 29:    if (error) { 30:       fprintf(stderr, "Could not find print_hello: %s\n", error); 31:       exit(1); 32:    } 33: 34:    (*hello)(); 35:    dlclose(library); 36:    return 0; 37: } 



       
    top
     


    Linux Application Development
    Linux Application Development (paperback) (2nd Edition)
    ISBN: 0321563220
    EAN: 2147483647
    Year: 2003
    Pages: 168

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net