[Home]  [Edit this page]  [Recent Changes]  [Special Pages]  [Help
Operating System » LoadingDynamicLibraryC
This article will explain a bit about the Linux library linking mechanism.
In section 1, we will start with writing a small program to examine difference between library linking strategies.
In section 2, we will write a program that deals with libraries itself.

1, Hello World Again!

Let's create a simple program, that writes out.. Hello World! (How unexpected ).

  1. include <stdio.h>
int main(void) { printf("Hello World!\n"); return 0; }


Well, we can compile this as usual,

gcc -Wall hello.c -o hello

in which case we get all the libraries linked to our program dynamically. This means, that the external functions we call are not compiled into our code, but reside in shared objects outside. These shared objects are loaded into and unloaded from memory as neccesary. We can check which libraries are linked dynamically to our program by typing

ldd hello

This will generate an output something similiar to:

libc.so.6 => /lib/libc.so.6 (0x40024000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)


We may also want to check out the size of our program. It is approximately 4 kilobytes long.

Now let's compile it this way:

gcc -Wall hello.c -o hellos -static

If we now try to retrieve information using ldd hellos, we get the message not a dynamic executable. If we check out the file size now, it will be around 400 kilobytes! This means, that no dynamic libraries are linked, all the functions are included in the executable.

This way, our program may work on a computer that doesn't have those libs, but will eat up lots of space. It is not a good idea to choose this method, because libraries often change (for example, bugs are fixed, speed is improved...), and they are mostly backward compatible.

2. Give me plugins!

You may want to write programs that are easy to extend with modules, without having to recompile the whole application. Shared objects provide you an easy way to do this. All you have to know is how to write a shared object and how to grab  ?resources from it.

Writing a shared object is nothing unusual, just leave the main() function, and compile with the -shared option. You could also include an _init() and a _fini() function to be executed automagically upon shared object loading/unloading, but tragically a naughty system library exports functions with the same name, so we cannot get our lib containing any of these functions to compile. Let's hope that this will shortly be corrected; until then you could call your own init and uninit function manually, if required.

So let's write a shared object called "x"!

  1. include <stdio.h>
  2. include <string.h>
/* Let's provide some info about this plugin to the main program */ char info[] = "sample plugin 1"; char version[] = "0.0a"; /* This function takes a string, and reverses it */ void misc(char *workbuffer) { unsigned int i, len = strlen(workbuffer); printf("X-plugin working..\n"); for ( i=0 ; i<len/2 ; i++ ) { char tmp = workbuffer[i]; workbuffer[i] = workbuffer[len-i-1]; workbuffer[len-i-1] = tmp; } }


Quite a simple one, isn't it? Let's compile with:

gcc -Wall x.c -o x.so -shared

But what is a plugin worth if nothing can use it? For writing a program using shared objects, we need to know the functions to operate on shared objects. These are defined in dlfcn.h. Let's see the functions:

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

Tries to open the shared object referenced by the path and name filename, with options signed by flag. The two most used flags are: RTLD_NOW to resolve  ?symbols from library at once before the return of the function, and RTLD_LAZY to resolve symbols in the runtime of the library. If everything is ok, returns a handle to this library.

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

Returns a pointer to the  ?symbol with the name symbol, located in the library referenced by handle.

int dlclose (void *handle)

Closes the library referenced by handle. This does not neccesarily mean unloading it, because if it was opened with dlopen() N times, the lib will only unload after N times of dlclose(). Of course, this has makes any sense only if multiple programs want to use the library. In this case, the plugin will only be used by ours. Returns 0 if ok.

const char *dlerror(void)

I didn't speak about handling errors yet. This functions returns a pointer to an error string. If it points to the address NULL, there were no errors. Be careful, do not examine the return value directly, but rather save it into a pointer and check that. This is because once called, dlerror() will clear the error status, thus, an immediate second call will always return NULL. If non-NULL, the pointed string stores a human-readable error message.

Now on, let's write test_plugins.c that will load all the plugins from a ./plugins directory, and call their misc() function.

/**
    • A sample program to load all the shared objects from the directory
    • "plugins" and call their misc() function
    • /
  1. include <stddef.h>
  2. include <stdio.h>
  3. include <sys/types.h>
  4. include <dirent.h>
  5. include <dlfcn.h>
  1. define PARENT_DIR ".."
  2. define CURRENT_DIR "."
  3. define PRG_VERSION "0.0a"
/* Prototype for the misc() function */ typedef void(*Misc)(char * buffer); /* A nice struct for a plugin */ struct Plugin { /* Shared object handle */ void *so; /* Misc() address, plugin version & info */ Misc misc; char *version; char *info; }; /* create an alias for easier usage*/ typedef struct Plugin Plugin; /* A function for checking libdl errors */ int dlok(const char *msg) { char *error; if ( error = dlerror() ) { printf("%s: %s\n", msg, error); return 0; } return 1; } /* Loads plugin named "soname" into the struct Plugin plugin*/ void *loadplugin(Plugin *plugin, const char *soname) { printf("Loading %s...\n", soname); /* Load the library, resolve symbols as they are needed. */ plugin->so = dlopen(soname, RTLD_LAZY); if ( plugin->so ) { /* Get the misc() address */ plugin->misc = dlsym(plugin->so, "misc"); if (!dlok("\\-misc() not found")) return 0; /* Display plugin info */ plugin->info = dlsym(plugin->so, "info"); if (!dlok("\\-info not found")) return 0; printf("Plugin info: %s\n", plugin->info); /* Check program-plugin version match */ plugin->version = dlsym(plugin->so, "version"); if (!dlok("\\-version not found")) return 0; if (strcmp(plugin->version, PRG_VERSION)) { printf("Non-%s plugin version: %s\n", PRG_VERSION, plugin->version); return 0; } } return plugin->so; } /* A seperated unloader */ int unloadplugin(Plugin *plugin) { return dlclose(plugin->so); } /* Define the directory containing plugins */ char plugindir[] = "./plugins"; int main(void) { /* Pointers for a directory and an entry */ DIR *dir; struct dirent *ep; /* A plugin */ Plugin plugin; /* A temporary buffer */ char buffer[200]; /* A buffer to work on */ char workbuffer[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; printf("Original workbuffer: %s\n", workbuffer); /* Grab the directory */ dir = opendir(plugindir); if (dir) { /* Read all the entries - say there are only shared objects inside */ while ( ep = readdir(dir) ) { /* Obviously skip these entries */ if ( !strcmp(ep->d_name, PARENT_DIR) || !strcmp(ep->d_name, CURRENT_DIR) ) continue; /* Try to load the plugin */ sprintf(buffer, "%s/%s", plugindir, ep->d_name); if ( loadplugin(&plugin, buffer) ) { /* Call the misc() func */ (*plugin.misc)(workbuffer); printf("Workbuffer after misc(): %s\n", workbuffer); /* Release the plugin */ unloadplugin(&plugin); } } /* Close the directory */ closedir(dir); } else { perror("Error opening dir \"./plugins\"\n"); return -1; } return 0; }


For compiling, we also need to [] the dynamic loader library. Let's do it.

gcc -Wall test_plugins.c -o test_plugins -ldl

Now make a directory called plugins, copy the x.so in there, and launch test_plugins.

Here I present an other example plugin y, to make our program more fun.

  1. include <stdio.h>
  2. include <string.h>
char info[] = "sample plugin 2"; char version[] = "0.0a"; /* Changes every second character of the given string to "-" */ void misc(char *workbuffer) { unsigned int i, len = strlen(workbuffer); printf("Y-plugin working..\n"); for ( i=0 ; i<len ; i++ ) if ( i%2 ) workbuffer[i] = '-'; }


Compile (do not forget the -shared switch), put the compiled y.so into the plugins dir, and have fun )

A sample output from my run:

Original workbuffer: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
Loading ./plugins/x.so...
Plugin info: sample plugin 1
X-plugin working..
Workbuffer after misc(): ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210
Loading ./plugins/y.so...
Plugin info: sample plugin 2
Y-plugin working..
Workbuffer after misc(): Z-X-V-T-R-P-N-L-J-H-F-D-B-9-7-5-3-1-


Final notes: In a real-world application, you would of course not release the shared object immediately (doing so would make your  ?resource pointers meaningless), just when you do not want to use any of its  ?resources anymore. The addresses of  ?resources may also be groupped by plugin, and arranged in an array... but you may know that.

Happy programming!

last edited (October 15, 2003) by philip7891, Number of views: 10918, Current Rev: 7 (Diff)

[Edit this page]  [Page history]  [What links here]  [Discuss this topic]  [Printer Friendly]  

Members

Username:

Password:


Register
Forgot Password?




Programmers Heaven - for .NET, Java, C/C++ and WEB Developers!
© 1996-2008 Community Networks Ltd. All rights reserved. Reproduction in whole or in part, in any form or medium without express written permission is prohibited. Violators of this policy may be subject to legal action. Please read Terms Of Use and Privacy Statement for more information. Site Management by Lars Hagelin at Kontantkort.se.