The concept of translation units allows a program to be divided into separately managed parts. A single programmer does not need to write all the translation units required to build a program, and can combine units possibly maintained by other programmers or organizations. A library is a set of translation units usually maintained by another body, plus any headers required to declare their contents. The translation units usually come in a pre-compiled form, and might not even originally have been written in C.

The maintainer will specify what entities are in the library and how they should be accessed or used. As long as both the maintainer and user adhere to the agreed specification, the library and the programs that use it can be managed independently. Indeed, C comes with a Standard Library of headers available to all C programs.

Using libraries

[ Work in progress : Just dumping notes here at the moment.]

The use of libraries is almost entirely platform-specific, and even specific to your build environment. However, it usually boils down to ensuring that some headers are accessible with #include, and that some precompiled translation units (modules) can be linked with the user's program. Such modules are usually grouped into archives or compendia that can be linked with a program as a single unit. These have suffixes such as .a, .so, .lib and .dll, depending on platform.

Command-line compilers often include an option such as -Idir to add a directory dir to a path which is searched for headers. If the program attempts #include <foo/bar.h>, the compiler will look for dir/foo/bar.h, for all dir specified by -I.

Another common command-line option is -Ldir to add directory dir to a path which is searched for precompiled modules. If an option such as -lfoo is used when linking, the linker will search for files such as dir/libfoo.a, dir/libfoo.so, dir\foo.lib and dir.o.foo (depending on platform), for all dir specified by -L.

There is some question over whether headers for external libraries should be obtained with #include <> or #include "". A strict interpretation of the standards would reserve <> for standard headers only. [Citation required?] On the other hand, <> prevents local files (i.e., relative to the #includeing file) from being included, so it exactly corresponds to what process you'd expect to use to obtain external headers, whether part of the C standard or not.

Writing libraries

[ Work in progress : Document.]
[ Work in progress : File structure: external headers, internal headers, source files.]

Abstraction in libraries

Your library should present an abstraction to the user (the API), and the implementation of that abstraction by the library should be hidden from the user. This allows you to develop and enhance the library without breaking compatibility with programs already using it. Compared to other languages, C does not provide much help in hiding the implementation, and you will sometimes be forced to expose some information, so good documentation is required to make the distinction between what is part of the abstraction and what is unintentionally exposed.

Use static to make functions and objects have internal linkage, to hide functionality and state within your library. Note that the keyword static actually hides functions and objects within a translation unit, so you can't use them if you need to share something between several units within the same library. In such cases, just declare such entities in an internal header.

Use pointers to incomplete types as a form of abstraction that allows the API to change without altering the ABI. For example, if you use a structure to store state, you can prevent it from being exposed like this:

// in external header
typedef struct context *context_t;
// in internal header
struct context {
  ...
};

Although this exposes that you're using a structure internally, no further information about the structure is exposed. Make it clear in documentation that context_t is to be used only as an opaque handle. You could use void *, but that loses some type safety, without really buying you much more abstraction.

The technique requires that your library manages the allocation of the context; you can't leave it to the user, as it always looks incomplete to them.

Support user customization of your library with function pointers. For example, if you have a function which initiates a task to exterminate all wombles from a park, it could be specified to call the user back when the task is complete:

// in external header

int exterm_wombles(womble_park *pk,
                   void (*done)(void));

Such functions are most useful when they take a generic pointer as context:

int exterm_wombles(womble_park *pk,
                   void (*done)(void *ctxt),
                   void *ctxt);

This function would be documented to invoke (*done)(ctxt) when all wombles have been exterminated from the specified park. The user can then provide any additional information to *done that was available when the task was initiated.

If several user functions must be called with the same context, you could employ a kind of poor-man's object oriented design to the user code. For example, to draw a shape onto a user-supplied canvas:

// in external header

struct canvas_type {
  void (*move)(void *canvas,
               const struct coords *to);
  void (*line)(void *canvas,
               const struct coords *to);
  void (*bezier4)(void *canvas,
                  const struct coords *to,
                  const struct coords *cp1,
                  const struct coords *cp2);
  void (*close)(void *canvas);
};

void draw_shape(shape *,
                const struct canvas_type *type,
                void *canvas);

Naming in libraries

Avoid the underscore _ as a leading character. It's tempting to use it for internal names that you're forced to expose:

#ifndef _MYLIB_INCLUDED
#define _MYLIB_INCLUDED

...

#endif

…but names beginning with an underscore are reserved for the implementation or even future standards. You might discover that your library is incompatible with some platforms, or future versions of current platforms, as a result.

C89 imposed very weak requirements on the mininum number of characters (6) that an implementation could use to distinguish external names, and did not require case sensitivity. Later standards have increased this to 31, and require case sensitivity. Many implementations will support much longer names, and you're unlikely to run into many that don't. The limited requirements allow for implementations on highly constrained hardware to conform. This means that you can afford to use a common prefix on external names. Prefixes of the form mylib_ or MyLib are commonly chosen.

Relative headers in libraries

Consider splitting up your declarations across several headers, engaging your library to different degrees. A header might contain only types, only functions, or only macros, allowing other users to define their own headers to pull in only the parts they need. A macro-only header might even be useful in another preprocessed language, or for compiling C to a different target.

Use forward slashes / U+002F for directory separators within #include directives, and let the compiler translate the header name into a file name according to local conventions. This allows the source code to remain the same whatever platform it's compiled on, without relying on conditional compilation. This isn't standard behaviour, but portable source code would be a mess without this fairly well established convention.

Unless your library has only a few headers, gather them together in a directory which is named in the #include directive, e.g., #include <foo/types.h>, #include <foo/ops.h>, etc. Within these headers, just use #include "types.h" rather than #include <foo/types.h>. While the library is being built, the former will usually include the correct types.h adjacent to the including file, while the latter might include an older, already installed version. If you have a more complex directory structure, you might need to use the likes of #include "../ops.h". The use of .. to refer to a parent directory seems to be well supported, but if you are unsure, don't forget that you can use the expansion of macros to determine exactly what to include. Although no standard requires compilers to behave exactly in these ways, it is in the interests of compiler writers to make them converge on this convention in order to reduce the need for such shenanigans, and few alternative (but still conforming) behaviours would make as much sense.

[ Work in progress : Perhaps a list of implementations that follow this convention would be helpful, plus a few counter-examples.]

Portability of libraries

[ Work in progress ]

CHaR
Sitemap Supported
Site format updated 2024-06-05T22:37:07.391+0000
Data updated 1970-01-01T00:00:00.000+0000
Page updated 2024-06-10T19:54:01.041+0000