Wednesday, April 30, 2008

Creating and Using .so Files With gcc

And what is an .so file you might ask? Those from a Windows background might know them as .dll files, but that still doesn't answer the question.

Normally, when you compile a program, the system takes all of the pieces of code that you've written, converts them into something the computer can understand (machine code) and glues them together. You might compare the process to building a pyramid. All of the stones that you've sculpted are joined together and stacked up. Just like with a pyramid, if you want to change one of the stones, everything that sits on top of it needs to be adjusted. With a C program, making changes in a piece of code means lots of pieces need to be recompiled and reglued together.

If you use dynamic linking, shared libraries, whatever you'd like to call them, things work a bit differently. With dynamic linking, the pieces are not all glued together or stacked up, instead they are plugged in when they are needed. As a result, changes to a piece of code don't require that lots of other pieces get reshaped (recompiled) to fit. With dynamic linking, it is possible to change a piece of code while the program is running. Funny, that sounds a bit like a scripting language.

Here's an example. Let's say I wrote a library with a function called SpecialPrint. (It's like printf but different!) Here is the code for speciallib.c:

void SpecialPrint(char* payload) {
printf("Special Print:%s\n", payload);
Now I write a program that uses this library. Here's the normal, non-dynamic, static linking, built like a pyramid way of using the library.

int main() {
printf("normal printf argument\n");
SpecialPrint("special print's argument");
With the above code, I compile the SpecialPrint library with my main code and link them together, producing a single executable file. However, I can use the same speciallib file with code that loads the library dynamically while the program is running. The code might look something like this:

void ShowError() {
char *dlError = dlerror();
if(dlError) printf("Error with dl function: %s\n", dlError);

int main() {
void *SharedObjectFile;
void (*SpecialPrintFunction)(char*);

// Load the shared libary;
SharedObjectFile = dlopen("./", RTLD_LAZY);
// Obtain the address of a function in the shared library.
SpecialPrintFunction = dlsym(SharedObjectFile, "SpecialPrint");

printf("normal printf argument\n");
// Use the dynamically loaded function.
(*SpecialPrintFunction)("special print's argument");

In the above, the show error function is optional, I added it so that any errors that occur would be displayed. When you compile the above code, you'll need to make the speciallib into an .so file and make sure that the speciallib's directory (the current directory in my example) is listed as one of the places that we should look for shared object files. Here's what the steps to compile look like:
export LD_LIBRARY_PATH=`pwd`
gcc -c -fpic speciallib.c
gcc -shared -lc -o speciallib.o
gcc main.c -ldl
For an explanation of what the above compiler options mean, and further explanation on .so files, see these two IBM articles on using shared object files on Solaris and linux.

Learning about .so files has been very interesting to me, because they bring a new level of flexibility to a traditionally rigid environment like C.
Post a Comment