Shared Library Mini-Tutorial

by Eric Huss
20000621

This is a short tutorial on how to create and use shared libraries using the GNU C Compiler on UNIX. Note that this information may not be appropriate for other compilers and does not discuss platform differences.

What is a shared library?

A shared library is a binary file that contains a set of callable C functions. The file is designed so that the code is position-independent, meaning that the code may be loaded anywhere in memory. An application may load and unload shared libraries at will. This process is handled by a interpreter (normally "ld.so") which links a shared object into your application at runtime.

Creating a Shared Library

To create a shared library, you write the code like you normally would. When you go to compile your code, you should indicate that the code needs to be "position independent". You do this with the gcc compile option "-fPIC". For example:
gcc -fPIC -c my_library.c
Now that you have some .o files, you can link them into a shared library. You do this with the gcc compile option "-shared".
gcc -shared -o libmylib.so my_library.o other_functions.o
You now have a shared library.

Shared Library Paths

In order to find a shared library, the dynamic linker looks in certain directories to find the library. Generally it will look in /usr/lib and /lib. Your system may also be configured to look in other directories (with ldconfig). If you have an environment variable called LD_LIBRARY_PATH it will check the directories listed there first (paths should be separated by colons).

Since during the development phase it is unlikely you'll be copying your library into /usr/lib as root, you'll probably need to use the LD_LIBRARY_PATH environment variable to control the location. It is also common for production applications to rely on this as well to prevent clutter in the /usr/lib directory.

Linking With a Shared Library

The simple way to use a shared library is to just link it like a normal library archive. For example:
gcc -o my_application my_application.o -lmylib
The -lmylib option tells the linker, "go look for libmylib.so" for the library. It will also try other variants on the filename such as libmylib.a and mylib.a, etc.

Your application now seemlessly uses the shared library.

Alternate method: Manually Loading a Shared Library

If you want to control the linking of the library yourself, you can use the "dl" interface. So, for example, if you have the shared library "libmylib.so", you first need to open a handle to that library, and then you need to retrieve function pointers from the shared library. A simple example is:
int main()
{
void * my_lib_handle;
int (*some_func)();

	my_lib_handle = dlopen("libmylib.so",RTLD_NOW);
	if(my_lib_handle==NULL) {
		/* ERROR HANDLING */
	}
	some_func = (int (*)()) dlsym(my_lib_handle,"some_function");
	if(some_func==NULL) {
		/* ERROR HANDLING */
	}
	printf("Return code is %i\n",(*some_func)());
	return 0;
}
The RTLD_NOW option when opening the library tells it to resolve all undefined symbols immediately. Visit the dlopen man page for more information.

The dlsym function finds a symbol within the given shared library handle, and gives you a pointer to it. Note that you can also get global variables besides just C functions.

When compiling your application, you need to link the dl library with -ldl on the linker command line. For example:

    gcc -o my_application my_application.c -ldl

Function Visibility

Inside the shared library, normally your functions can not view any data inside your application, and your application must call dlsym() in order to find functions in the shared library. There is an alternate method that allows your shared library to find external functions. This involves using the RTLD_GLOBAL option when opening the library. Any subsequent library you open will now be able to seemlessly use the functions opened in the first shared library (which has the RTLD_GLOBAL option). For example:

int main()
{
void * util_handle;
void * my_lib_handle;
int (*some_func)();

	util_handle = dlopen("libutil.so",RTLD_NOW|RTLD_GLOBAL);
	if(util_handle==NULL) {
		/* ERROR HANDLING */
	}

	my_lib_handle = dlopen("libmylib.so",RTLD_NOW);
	if(my_lib_handle==NULL) {
		/* ERROR HANDLING */
	}
	some_func = (int (*)()) dlsym(my_lib_handle,"some_function");
	if(some_func==NULL) {
		/* ERROR HANDLING */
	}
	printf("Return code is %i\n",(*some_func)());
	return 0;
}
Now, the code in the libmylib.so library will be able to "see" all the functions in the libutil.so library. Note that this is the same as specifying "-lutil" on the command line, so you don't necessarily need to manually open the shared library.

Making C++ Shared Libraries

In order to work with C++, you need to understand C++ name mangling. A C++ compiler essentially converts all your method calls into C functions with the name mangled so that it resolves to the correct method. In general, you do not need to do anything special to use C++ libraries, but you need to be careful when mixing straight C and C++. If you are calling and compiling C code with a C++ compiler, you need to make sure that the C++ compiler does not mangle the name by wrapping your C function declarations such as:
#ifdef  __cplusplus
extern "C" {
#endif

int my_c_function();

#ifdef  __cplusplus
}
#endif

Cleaning Up

When you are done with a shared library, you may free up the memory it uses by calling dlclose. Only call this if your application doesn't plan to use the library any more. You may also get error information by calling dlerror. Visit the dlclose and dlerror man pages for more information.


Please send comments to Eric Huss