Static Libraries

Back

What is a library? What are they for?

How do I create a library?

How do I use a library?

How should I organise code within the library?

Where should I keep my libraries and header files?

What is a library? What are they for?

In computer programming jargon, a library is a repository for object code, organised in a way that permits re-use of that object code (whether or not you have the original source code). Once the object code is compiled by a particular implementation, it can be stored in the library, which can then be linked into as many programs as you like. When you link the library to the program, any object code required by the program is copied from the library into the program. The benefits of using libraries are:

  • You don't have to keep copying source code all over the place
  • You don't have to keep re-compiling the same code over and over
  • You can provide functionality to other programmers without releasing source

This last-named benefit may seem to Open Source developers to be more of a disadvantage, of course, but that's a socio-economic issue, not a technical issue!

How do I create a library?

The precise mechanism for creating a library varies depending on your library-building tool. In this tutorial (which is at a very basic level, and is intended to get you up and running, rather than to make you an expert), I'll show you how to do this using each of three popular modern library-building tools on two different operating systems (well, sort of three - but OS X is mostly like Linux in this regard - I'll point out where they differ, though). But first, we'll need some code to put into our library. To make it very clear that there's nothing special about library code, I'm going to use the simplest possible example code I can think of:


/* foo.c */
unsigned int halve(unsigned int n)
{
  return n / 2;
}
/* end of foo.c */

This code implements the halve function (in C, this is formally known as a function definition). But it is not sufficient. As well as an implementation, we need an interface, so that other code can interact cleanly with this code. We store our interface in a header.


/* foo.h */
#ifndef H_FOO_
#define H_FOO_ 1

/* Functions:
 * halve() : returns half the value of the input
 */
unsigned int halve(unsigned int n);
#endif

/* end of foo.h */

That's all the caller needs to know. Now let's compile our source, and stick the resulting object file into a library. I'll demonstrate using command line tools for various implementations. If you would rather use a GUI, and can't find out how to drive your particular GUI, ask on Usenet (or just use the command line, which is generally much easier!).

First, compile foo.c as follows:

Linux

gcc -W -Wall -ansi -pedantic -O2 -c -o foo.o foo.c

Note the -c switch. This means "just compile, don't link". That's rather important (especially as foo.c doesn't have a main() function!).

Borland

bcc32 -A -c foo.c

-A just means "turn on those ANSI rules".

Microsoft

cl -W4 -Za -c foo.c

-Za means "disable Microsoft extensions".

Next, let's create the library.

Linux

ar cr libmycode.a foo.o

The "c" means "create the library"; if the library already exists, omit this flag. The "r" means "insert the file into the archive, replacing an existing file if necessary". Note that I've named the library "libmycode.a". There's a reason it starts with "lib", which we'll come to in due course.

OS X

According to information received, this is the same as for Linux, except that you must call ranlib after ar:

ranlib libmycode.a

suffices, apparently.

Borland

tlib myborlandcode.lib /C +foo.obj

/C means "make the library case-sensitive". Since C is a case-sensitive language, this is a very good idea!

The + means "add", so +foo.obj means "add foo.obj to the library".

Microsoft

lib /out:mymscode.lib foo.obj

How do I use a library?

To use the library, we'll need a program. Here's one:


#include <stdio.h>
#include "foo.h"
int main(void)
{
  printf("%u\n", halve(6U));
  return 0;
}

Save this as testfoo.c. I will assume that, for the time being, you're using the same directory as you used to build the library. That's not a constraint on library usage; it's just that I can only explain one thing at a time. Bear with me.

Let's build the program:

Linux


gcc -W -Wall -ansi -pedantic -O2 -L"." -o testfoo testfoo.c -lmycode

The quotes around the full-stop (period) are optional. The -L switch means "look for libraries along these paths, as well as wherever it is you usually look", and of course "." means "this directory right here". The -lmycode switch means "link in a library called libmycode.a". Yes, the linker expands the library name all by itself, so make sure that the expansion is done correctly, by paying careful attention to the real name of the library - start it lib and finish it .a for best results.

Clearly, you can keep libmycode.a anywhere on your filesystem, as long as you specify the -L switch correctly. If you have write access to /usr/local/lib, you may want to place your library file in there, so that you can use it conveniently in all your programs.

OS X

As for Linux, apparently, except that I'm told that /opt/local/include and /opt/local/lib are more appropriate than the /usr equivalents.

Borland

bcc32 -A testfoo.c myborlandcode.lib

Microsoft

cl -W4 -Za testfoo.c mymscode.lib

In each case, the program is built correctly, and testfoo can call the halve() function, despite the compiler not having access to the source code of halve() at the time that testfoo is built.

But we're not finished quite yet. The library only has one function in it. If I went to my local lending library and found that they had only one book in stock, I'd probably be a bit disappointed (unless it was a really good book!). Let's find out how to add new object files to existing libraries.

Here's another function:


/* bar.c */
unsigned int quarter(unsigned int n)
{
  return n / 4;
}
/* end of bar.c */

You may wish to knock up a bar.h file, or (since quartering is clearly related to halving) simply add the prototype to foo.h. Then, compile this to either bar.o (Linux) or bar.obj (Borland, Microsoft). Here's how to add it to the existing library:

Linux

ar r libmycode.a bar.o

Borland

tlib myborlandcode.lib +bar.obj

To replace an existing object file in the library, by the way, use -+filename.obj instead. If you don't do this, Borland plays safe and refuses to update the file.

Microsoft

Apparently (and I haven't tested this yet) you can add an object file to an existing Microsoft library by letting the library be one of the inputs to the lib command, with the new object(s) being the other input(s):

lib /out:mymscode.lib mymscode.lib bar.obj

How should I organise code within the library?

Well, it's up to you. Be aware, however, that some linkers (very possibly including the one you use) aren't able to extract functions from libraries. They can only extract object files from libraries. Thus, if your program needs just one function from a bunch of five all in the same object file, you could well end up with a bigger binary than necessary.

"Well, yes, all right," you say, "but why is that a problem? After all, I could just write one function in each object file." Absolutely right. You can do that, and that's fine. So, there's one way you might organise your code to make it linker-friendly; just put one function in each object file. (The downside is that you end up with a lot of source files, and this increases the cost (or hassle, if you prefer) of file management.)

In any case, there are times when functions are pretty well bound to be used together in a well-written program. Examples that spring to mind include FindFirstFoo() and FindNextFoo(), or CreateWibble() and DestroyWibble(). Or maybe your externally-exposed function needs several static helper functions. These are good reasons to include more than one function in a particular object file.

It's entirely up to you, though.

Where should I keep my libraries and header files?

Again, this is up to you. Here's a suggestion which you are entirely free to adopt, adapt, or ignore entirely:

Whilst the library is under development, treat it as you treat any other project.

Once it's settled down sufficiently that modifications of its existing functions are likely to be rare, copy it to a well-known location. On Linux, the obvious place is /usr/local/lib. On Windows, it's less clear-cut. I use d:\alldata\rjh\dev\lib for mine. At the same time, copy the relevant header(s) to an appropriate include directory (/usr/local/include or d:\alldata\rjh\dev\include, in my case). Adjust your development tools, via switches or GUI settings or whatever, to check these places when compiling and linking code.


That's it. Suggestions for improvements to this page are most welcome. Kudos to EwIck for sorting out how to add object files to existing libraries in Microsoft C.