[Home]
[Edit this page]
[Recent Changes]
[Special Pages]
[Help]
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
).
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"!
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.
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.
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!
[Edit this page] [Page history] [What links here] [Discuss this topic] [Printer Friendly]
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
int main(void) { printf("Hello World!\n"); return 0; }
- include <stdio.h>
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"!
/* 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; } }
- include <stdio.h>
- include <string.h>
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
- /
- include <stddef.h>
- include <stdio.h>
- include <sys/types.h>
- include <dirent.h>
- include <dlfcn.h>
- define PARENT_DIR ".."
- define CURRENT_DIR "."
- define PRG_VERSION "0.0a"
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.
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] = '-'; }
- include <stdio.h>
- include <string.h>
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!
[Edit this page] [Page history] [What links here] [Discuss this topic] [Printer Friendly]
