1. Overview

We may sometimes need to convert a static library to a shared library. For example, we may prefer using a shared library to decrease the size of executables.

In this tutorial, we’ll discuss how to convert several static libraries together with object files to a single shared library.

2. Example Setup

We’ll use two static libraries and one object file in our example.

2.1. Code Example

One of the source files we’ll use is the C program, foo1.c:

#include <stdio.h>
#include "foo1.h"

void foo1() {
    printf("foo1\n");
}

The foo1() function in the source code just prints foo1.

Its header file is foo1.h:

#ifndef _FOO1_H_
#define _FOO1_H_

void foo1();

#endif

The other source files, foo2.c and foo3.c, and the corresponding header files, foo2.h and foo3.h, are similar:

$ cat foo2.c
#include <stdio.h>
#include "foo2.h"

void foo2() {
    printf("foo2\n");
}
$
$ cat foo3.c
#include <stdio.h>
#include "foo3.h"

void foo3() {
    printf("foo3\n");
}

$ cat foo2.h
#ifndef _FOO2_H_
#define _FOO2_H_

void foo2();

#endif

$ cat foo3.h
#ifndef _FOO3_H_
#define _FOO3_H_

void foo3();

#endif

Therefore, we have the following files in the current directory:

$ ls
foo1.c  foo1.h  foo2.c  foo2.h  foo3.c  foo3.h

2.2. Building the Object File

First, we’ll build the object files using gcc:

$ gcc –fPIC –c foo1.c
$ gcc –fPIC –c foo2.c
$ gcc –fPIC –c foo3.c

The -c option of gcc compiles the given source file and generates an object file instead of creating an executable. If we don’t specify the name of the target object file, an object file with the same name but with the .o suffix is generated by default. For example, gcc produces foo1.o for foo1.c.

The -fPIC option of gcc is necessary for generating position-independent code. The generated machine code isn’t dependent on being located at a specific address to work. This is required when we use the generated code in a shared library.

Let’s check once more the files in the current directory:

$ ls
foo1.c  foo1.h  foo1.o  foo2.c  foo2.h  foo2.o  foo3.c  foo3.h  foo3.o

gcc generated the object files foo1.o, foo2.o, and foo3.o as expected.

2.3. Building the Static Libraries

Now, let’s build the static libraries, libfoo1.a and libfoo2.a:

$ ar crs libfoo1.a foo1.o
$ ar crs libfoo2.a foo2.o

We use the ar command to build the static libraries. ar is useful for creating, updating, and extracting from archives. An archive is a collection of other files. These files are the members of the archive. For example, libfoo1.a is the archive file and foo1.o is a member of it.

The -c option of ar is for creating an archive. The -r option is for replacing the member of the archive if the member already exists within the archive. Finally, the -s option is for creating an index to the contents of an archive and storing it in the archive, which speeds up linking the library.

We can specify the options of ar without a hyphen, as well.

Let’s check the files in the current directory again:

$ ls
foo1.c  foo1.h  foo1.o  foo2.c  foo2.h  foo2.o  foo3.c  foo3.h  foo3.o libfoo1.a  libfoo2.a

We’ve built the static libraries, libfoo1.a and libfoo2.a, successfully.

3. Generating the Shared Library

Having generated the static libraries and the object file in the previous section, it’s time to build the shared library:

$ gcc –shared –o libfoo.so foo3.o –Wl,--whole-archive libfoo1.a libfoo2.a –Wl,--no-whole-archive

The -shared option of gcc is for building a shared library, which can be linked with other objects to create an executable. The -o option specifies the name of the generated library. It’s libfoo.so in our example.

The linker discards the symbols and object files in static libraries that aren’t used. Therefore, we must use the -Wl,–whole-archive option to include the entire static library during linkage. We use the -Wl,–nowhole-archive flag to specify the end of used static libraries.

Let’s check the files in the current directory now:

$ ls
foo1.c  foo1.h  foo1.o  foo2.c  foo2.h  foo2.o  foo3.c  foo3.h  foo3.o libfoo1.a  libfoo2.a  libfoo.so

We now see the generated shared library, libfoo.so.

Let’s check the existence of the functions in libfoo.so using the nm command:

$ nm libfool.so | grep foo1
000000000000111f T foo1
$ nm libfool.so | grep foo2
0000000000001135 T foo2
$ nm libfool.so | grep foo3
0000000000001109 T foo3

The nm command lists the symbols in an executable. We filtered the output of nm using grep to print only the information for foo1, foo2, and foo3. The first column in the output of nm shows the virtual address of the symbol. The second column shows a character depicting the symbol type. Finally, the T characters in our example show that our functions are in the text (code) section as expected.

Therefore, we included the symbols in the static libraries and object files to the shared library successfully.

4. Using the Shared Library

We’ll check the operation of the shared library by using it with the following C program, main.c:

#include "foo1.h"
#include "foo2.h"
#include "foo3.h"

int main()
{
    foo1();
    foo2();
    foo3();

    return 0;
}

This program calls the functions foo1(), foo2() and foo3(), and then exits.

First, let’s build the program:

$ gcc -o main main.c -L. -lfoo

The output of the generated executable is main because of the -o main part of the command. We specify the option -lfoo to link main with libfoo.so. The -L option adds the specified path to the directories that the linker uses for finding the libraries.

The linker searches a standard list of directories for finding a library. The “-L.” in our example adds the current directory to the search path so that the linker can find libfoo.so.

Now, we’ll run the program, main. But we must first add the current directory to the $LD_LIBRARY_PATH environment variable so that main can find libfoo.so at run-time:

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
$ main
foo1
foo2
foo3

As we can see, the program called the functions in the shared library, libfoo.so, successfully.

5. Conclusion

In this article, we discussed how to convert several static libraries together with object files to a single shared library.

First, we built two static libraries and an object file. We learned that generating position-independent code is necessary for building shared libraries. We used the ar command to build the static libraries.

Then, we built the shared library using the static libraries and the object file. We learned that we must use the -shared option of gcc to build a shared library. Additionally, we learned that using the -Wl–whole-archive option for the linker is critical for adding every object file in the archive to the shared library. We used the nm command to check the objects in the shared library.

Finally, we used the shared library in a program.