Shared libraries

An example

This section contains the process by which a small specialized shared library is created and built. We refer to the guidelines given earlier in this chapter.

The original source

The name of the library to be built is libmaux (for math auxiliary library). The interface consists of three functions, an external variable, and a header file.

The three functions are:

floating-point logarithm to a given base; defined in the file log.c

evaluate a polynomial; defined in the file poly.c

return usage counts for the other two routines in a structure; defined in stats.c,

The external variable is:

set to non-zero if there is an error in the processing of any of the functions in the library and set to zero if there is no error (unlike errno in the C library),

The header file is:

declares the return types of the function and the structure returned by maux_stat.

The source files before any modifications for inclusion in a shared library follow.

File log.c

   /* log.c */
   #include "maux.h"
   #include <math.h>

/* * Return the log of "x" relative to the base "a". * * logd(base, x) := log(x) / log(base); * where "log" is "log to the base E". */

double logd(base, x) double base, x; { extern int stats_logd; extern int total_calls;

double logbase; double logx;

total_calls++; stats_logd++;

logbase = log((double)base); logx = log((double)x); if(logbase == -HUGE || logx == -HUGE) { mauxerr = 1; return(0); } else mauxerr = 0; return(logx/logbase); }

File poly.c


/* poly.c */

#include "maux.h" #include <math.h>

/* Evaluate the polynomial * f(x) := a[0] * (x ^ n) + a[1] * (x ^ (n-1)) + ... + a[n]; * Note that there are N+1 coefficients! * This uses Horner's Method, which is: * f(x) := (((((a[0]*x) + a[1])*x) + a[2]) + ...) + a[n]; * It's equivalent, but uses many fewer operations * and is more precise. */ double polyd(a, n, x) double a[]; int n; double x; { extern int stats_polyd; extern int total_calls; double result; int i;

total_calls++; stats_polyd++; if (n < 0) { mauxerr = 1; return(0); } result = a[0]; for (i = 1; i <= n; i++) { result *= (double)x; result += (double)a[i]; } mauxerr = 0; return(result); }

File stats.c

   /* stats.c */
   #include "maux.h"

int total_calls = 0; int stats_logd = 0; int stats_polyd = 0;

int mauxerr;

/* Return structure with usage stats for functions in library * or 0 if space cannot be allocated for the structure */ struct mstats * maux_stat() { extern char * malloc(); struct mstats * st;

if((st = (struct mstats *) malloc(sizeof(struct mstats))) == 0) return(0); st->st_polyd = stats_polyd; st->st_logd = stats_logd; st->st_total = total_calls; return(st); }

Header File maux.h


/* maux.h */

struct mstats { int st_polyd; int st_logd; int st_total; };

extern double polyd(); extern double logd(); extern struct mstats * maux_stat();

extern int mauxerr;

Choosing region addresses and the target pathname

First, we choose the region addresses for the library's .text and .data sections from the segments reserved for private use on the 80386 Computer. Note that the region addresses must be on a segment boundary (4 MB):

   .text    0xB0600000
   .data    0xB0a00000
Also we choose the pathname for our target library:

NOTE: The choice of region addresses can be important. See the comments in ``Step 1: choosing region addresses''. A table of existing and suggested addresses is also given there.

Selecting library contents

This example is for illustration purposes, and so we will include everything in the shared library. In a real case, it is unlikely that you would make a shared library with these three small routines, unless you had many programmers using them frequently.

Rewriting existing code

According to the guidelines given earlier in the chapter, you need to first minimize the global data. We realize that total_calls, stats_logd, and stats_polyd do not need to be visible outside the library, but are needed in multiple files within the library. Hence, we will use the #hide directive in our specification file to make these variables static after the shared library is built.

We need to define text and global data in separate source files. The only piece of global data we have left is mauxerr, which we will remove from stats.c and put in a new file maux_defs.c. We will also have to initialize it to zero, since shared libraries cannot have any uninitialized variables.

Next, we notice that there are some references to symbols that we do not define in our shared library (i.e. log and malloc). We can import these symbols. To do so, we create a new header file, import.h, which will be included in each of log.c, poly.c, and stats.c. The header file defines C preprocessor macros for these symbols to make transparent the use of indirection in the actual C source files. Use the _libmaux_ prefixes on the pointers to the symbols because those pointers are made external, and the use of the library name as a prefix helps prevent name conflicts.

   /* New header file import.h */
   #define malloc 	(*_libmaux_malloc)
   #define log	(*_libmaux_log)

extern char * malloc(); extern double log();

Now, you need to define the imported symbol pointers somewhere. You have already created a file for global data maux_defs.c, so add the definitions to that.

   /* Data file maux_defs.c */

int mauxerr = 0; double (*_libmaux_log)() = 0; char * (*_libmaux_malloc)() = 0;

Finally, you observe that there are floating-point operations in the code, and you remember that the routines for these cannot be imported. (If you tried to write the specification file and build the shared library without taking this into account, mkshlib would give you errors about unresolved references.) This means you will have to use the #objects noload directive in our specification file to search the C host shared library to resolve the references.

Writing the specification file

This is the specification file for libmaux:

    1  ##
    2  ## - libmaux specification lfile
    3  #address .text 0xB0680000
    4  #address .data 0xB06a0000
    5  #target /my/directory/libmaux_s
    6  #branch
    7  	polyd		1
    8  	logd		2
    9  	maux_stat	3
   10  #objects
   11  	maux_defs.o
   12  	poly.o
   13  	log.o
   14  	stats.o
   15  #objects noload
   16  	-lc_s
   17  #hide linker *
   18  #export linker
   19  	mauxerr
   20  #init maux_defs.o
   21  	_libmaux_malloc	malloc
   22  	_libmaux_log	log

Briefly, this is what the specification file does. Lines 1 and 2 are comment lines. Lines 3 and 4 give the virtual addresses for the shared library text and data regions, respectively. Line 5 gives the pathname of the shared library on the target machine. The target shared library must be installed there for a.out files that use it to work correctly. Line 6 contains the #branch directive. Line 7 through 9 specify the branch table. They assign the functions polyd(), logd(), and maux_stat() to branch table entries 1, 2, and 3. Only external text symbols, such as C functions, should be placed in the branch table.

Line 10 contains the #objects directive. Lines 11 through 14 give the list of object files that will be used to construct the host and target shared libraries. When building the host shared library archive, each file listed here will reside in its own archive member. When building the target library, the order of object files will be preserved. The data files must be first. Otherwise, an addition of static data to poly.o, for example, would move external data symbols and break compatibility.

Line 15 contains the #objects noload directive, and line 16 gives information about where to resolve the references to the floating-point routines.

Lines 17 through 19 contain the #hide linker and #export linker directives, which tell what external symbols are to be left external after the shared library is built. Together, these #hide and #export directives say that only mauxerr will remain external. The symbols in the branch table and those specified in the #init directive will remain external by definition.

Line 20 contains the #init directive. Lines 21 and 22 give imported symbol information for the object file maux_defs.o. You can imagine assignments of the symbol values on the right to the symbols on the left. Thus _libmaux will hold a pointer to malloc, and so on.

Building the shared library

Now, you have to compile the .o files as you would for any other library:

   cc  -c  maux_defs.c  poly.c  log.c stats.c
Next, you need to invoke mkshlib to build our host and target libraries:
   mkshlib -s -t libmaux_s -h libmaux_s.a
Presuming all of the source files have been compiled appropriately, the mkshlib command line shown above will create both the host library, libmaux_s.a, and the target library, libmaux_s. Before any a.out files built with libmaux_s.a can be executed, the target shared library libmaux_s must be moved to /my/directory/libmaux_s as specified in the specification file.

Using the shared library

To use the shared library with a file, x.c, which contains a reference to one or more of the routines in libmaux, issue the following command line:

cc x.c libmaux_s.a -lm -lc_s

This command line causes the following:

The most important thing to note from the command line, however, is that you have to specify the C host shared library (in this case with the -lc_s) on the command line, since libmaux was built with direct references to the floating-point routines in that library.

Previous topic: Dealing with incompatible libraries

© 2003 Caldera International, Inc. All rights reserved.
SCO OpenServer Release 5.0.7 -- 11 February 2003