[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7. Tips for interface design

Writing a good library interface takes a lot of practice and thorough understanding of the problem that the library is intended to solve.

If you design a good interface, it won't have to change often, you won't have to keep updating documentation, and users won't have to keep relearning how to use the library.

Here is a brief list of tips for library interface design, which may help you in your exploits:

Plan ahead

Try to make every interface truly minimal, so that you won't need to delete entry points very often.

Avoid interface changes

Some people love redesigning and changing entry points just for the heck of it (note: renaming a function is considered changing an entry point). Don't be one of those people. If you must redesign an interface, then try to leave compatibility functions behind so that users don't need to rewrite their existing code.

Use opaque data types

The fewer data type definitions a library user has access to, the better. If possible, design your functions to accept a generic pointer (which you can cast to an internal data type), and provide access functions rather than allowing the library user to directly manipulate the data. That way, you have the freedom to change the data structures without changing the interface.

This is essentially the same thing as using abstract data types and inheritance in an object-oriented system.

Use header files

If you are careful to document each of your library's global functions and variables in header files, and include them in your library source files, then the compiler will let you know if you make any interface changes by accident (see section Writing C header files).

Use the static keyword (or equivalent) whenever possible

The fewer global functions your library has, the more flexibility you'll have in changing them. Static functions and variables may change forms as often as you like… your users cannot access them, so they aren't interface changes.

Be careful with array dimensions

The number of elements in a global array is part of an interface, even if the header just declares extern int foo[];. This is because on i386 and some other SVR4/ELF systems, when an application references data in a shared library the size of that data (whatever its type) is included in the application executable. If you might want to change the size of an array or string then provide a pointer not the actual array.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.1 Writing C header files

Writing portable C header files can be difficult, since they may be read by different types of compilers:

C++ compilers

C++ compilers require that functions be declared with full prototypes, since C++ is more strongly typed than C. C functions and variables also need to be declared with the extern "C" directive, so that the names aren't mangled. See section Writing libraries for C++, for other issues relevant to using C++ with libtool.

ANSI C compilers

ANSI C compilers are not as strict as C++ compilers, but functions should be prototyped to avoid unnecessary warnings when the header file is #included.

non-ANSI C compilers

Non-ANSI compilers will report errors if functions are prototyped.

These complications mean that your library interface headers must use some C preprocessor magic in order to be usable by each of the above compilers.

`foo.h' in the `demo' subdirectory of the libtool distribution serves as an example for how to write a header file that can be safely installed in a system directory.

Here are the relevant portions of that file:

 
/* BEGIN_C_DECLS should be used at the beginning of your declarations,
   so that C++ compilers don't mangle their names.  Use END_C_DECLS at
   the end of C declarations. */
#undef BEGIN_C_DECLS
#undef END_C_DECLS
#ifdef __cplusplus
# define BEGIN_C_DECLS extern "C" {
# define END_C_DECLS }
#else
# define BEGIN_C_DECLS /* empty */
# define END_C_DECLS /* empty */
#endif

/* PARAMS is a macro used to wrap function prototypes, so that
   compilers that don't understand ANSI C prototypes still work,
   and ANSI C compilers can issue warnings about type mismatches. */
#undef PARAMS
#if defined (__STDC__) || defined (_AIX) \
        || (defined (__mips) && defined (_SYSTYPE_SVR4)) \
        || defined(WIN32) || defined(__cplusplus)
# define PARAMS(protos) protos
#else
# define PARAMS(protos) ()
#endif

These macros are used in `foo.h' as follows:

 
#ifndef FOO_H
#define FOO_H 1

/* The above macro definitions. */
#include "…"

BEGIN_C_DECLS

int foo PARAMS((void));
int hello PARAMS((void));

END_C_DECLS

#endif /* !FOO_H */

Note that the `#ifndef FOO_H' prevents the body of `foo.h' from being read more than once in a given compilation.

Also the only thing that must go outside the BEGIN_C_DECLS/END_C_DECLS pair are #include lines. Strictly speaking it is only C symbol names that need to be protected, but your header files will be more maintainable if you have a single pair of of these macros around the majority of the header contents.

You should use these definitions of PARAMS, BEGIN_C_DECLS, and END_C_DECLS into your own headers. Then, you may use them to create header files that are valid for C++, ANSI, and non-ANSI compilers(6).

Do not be naive about writing portable code. Following the tips given above will help you miss the most obvious problems, but there are definitely other subtle portability issues. You may need to cope with some of the following issues:


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by System Administrator on February, 19 2008 using texi2html 1.70.