How to create a shared library using SAS/C




With SAS/C 6.0 or greater you're able to create shared libraries. Actually, this feature exists since version 5.0, but now the process is more reliable and better documented.

Let's examine the necessary steps in order to make your library. We'll do this summarily at first, then we'll see a practical example.




To create a shared library, first you must write the code of the functions it will contain. You can spread this code among various files, just like you do with linker libraries, but you also must specify, in it, the registers in which each function gets its arguments. This can be obtained using SAS/C's __asm and register __xx keywords.

The next step is to write the .fd file for the library, a simple ASCII file whose purpose is to describe the order and parameters of the functions found in the library.

To transform the source code into a working .library file, you only have to compile and link it, making sure you specify some (simple) additional parameters. Such parameters are the following:

For the compiler
ParameterComment
LIBCODE Mandatory
SAVEDS Alternatively you can use the __saveds keyword

For the linker
ParameterComment
FROM LIB:libent.o LIB:libinit.o Mandatory, startup modules
LIBPREFIX <_prefix> Mandatory, function prefix
LIBFD <filename.fd> Mandatory, .fd file for the library
LIBID <id> Optional, library ID string
LIBVERSION <version> Optional, library version
LIBREVISION <revision> Optional, library revision

Lastly, it's necessary to use the FD2Pragma program supplied with SAS/C to obtain, from the .fd file, the include file with the #pragma directives for the pre-processor (needed to actually use the library in applications written in C),




For experienced Amiga C programmers, what has been said is probably enough to understand how to create a library. Neophytes, however, will find it useful to have an example clarifying the above concepts.




Let's say we want a library with three functions, called One(), Two() and Three(), all of them with only one argument.

The idea is as follows:

To this purpose, we could write the following code (let's assume, for now, graphics.library and intuition.library are already open):

#include "graphics/text.h"
#include "proto/graphics.h"
#include "proto/intuition.h"

int __asm PR_One(register __d1 int a)
{
   return (a);
}

int __asm PR_Two(register __d1 int a)
{
   struct TextFont *tf;
   struct TextAttr ta = { "topaz.font", 8, 0, 0 };

   if (tf = OpenFont(&ta))
   {
      CloseFont(tf);
      return (a * 2);
   }
   else
   {
      return (0);
   }
}

int __asm PR_Three(register __d1 int a)
{
   DisplayBeep(NULL);
   return (a * 3);
}

The PR_ prefix before the function names can be useful for reasons explained later; anyway, it's completely arbitrary, i.e. we could have used any other prefix.

The __asm and register keywords are used to tell the compiler that our functions get their arguments in registers, rather than on the stack, whereas __d1 means that in our case the CPU register D1 has to be used.
Each parameter of the future library's functions must have the corresponding register specified with a register __dn (or register __an) couple; n stands for a digit between 0 and 7.
By the way, it is advisable to use Dn registers for numeric arguments and An registers for pointer-type arguments.

Once you've written the library code, you must also write the .fd file with the description of each function and its parameters.

In our case we could write the following .fd file:

##base _TestBase
##bias 30
One(n)(D1)
Two(n)(D1)
Three(n)(D1)
##end

where we used TestBase as the name of the library base. For our purposes it's not necessary to understand the meaning of the ##bias directive; suffice to say the associated value is nearly always 30.
Let's suppose we save this .fd file with the name test_lib.fd.

To create the library we have now only to compile and link our code.
Compilation can be performed in the usual way, but you must add the LIBCODE and SAVEDS keywords in the command line.

For instance, if we called our source code Test.c, we'll write:

SC LIBCODE SAVEDS [other possible options] Test.c

and we'll get, if there weren't errors in the code, the object module Test.o.

The SAVEDS parameter can be omitted if (and only if) you make sure to add the analogous __saveds keyword before function names in your code, for example:

int __saveds __asm PR_One(register __d1 int a)

This alternative is useful when the code contains many local-only service functions; in this case the global usage of SAVEDS in the compiler call would create somewhat of an overhead, because local functions don't need it.

The linking must be performed with the following command:

SLink FROM LIB:libent.o LIB:libinit.o Test.o TO LIBS:test.library
      LIB LIB:sc.lib LIBPREFIX _PR_ LIBFD test_lib.fd

where test.library is the hypothetical name of the new library.

As you see, after the LIBPREFIX keyword you must specify the prefix found before the name of all library functions (if such a prefix does exist).
In our example the prefix is PR_, with an additional leading underscore ('_'), because the compiler always adds one to function names in the object module (remember, now we're using the linker, which operates on object modules rather than source code files).

Note (see above) that the prefix you use in the source code must NOT appear in the .fd file; furthermore, if you don't use any prefix, you have to specify a '_' after LIBPREFIX anyway (of course without quotes).

The usefulness of the prefix is that it allows you to write ASM modules with interface functions (stubs) between the C language and the library routines, that is functions copying appropriate register values onto the stack. Without the prefix it wouldn't be possible to name the interface functions the same as the real ones!
Actually, this feature is now obsolete, as SAS/C can now handle library function calls directly with the pre-processor #pragma directive, however the prefix may still be useful, at least to be able to distinguish at a glance, in your code, between "public" library functions and internal, local-use ones.

But let's go back to our linking command.

After the LIBFD keyword you must specify the name of the .fd file you created for the new library, in our example test_lib.fd.

Then you can add, of course, other options to the command, such as the usual NOICONS, SMALLCODE, and so on.

If all was done the right way, now the file test.library should have been created in the system LIBS: directory.

This example is not really complete because we didn't manage to open the two system libraries (graphics and intuition) needed by our test.library to work.

We could solve this problem by putting the library opening and closing code directly into the Two() and Three() functions, but this would create some overhead. It is much more efficient to ensure that the necessary libraries get opened only once at the beginning, and closed only once at the end.

To this purpose we can take advantage of a feature of SAS/C: it allows to redefine two internal functions, __UserLibInit() and __UserLibCleanup(), that are automatically executed when our library opens and closes itself.

__UserLibInit() is called when the library gets opened with OpenLibrary(), while __UserLibCleanup() is invoked after the library is closed with CloseLibrary().

It is therefore more convenient to open the needed libraries (and, more generally, to allocate all global resources) within __UserLibInit(), and close (or deallocate) everything within __UserLibCleanup().

The rule is that __UserLibInit() returns the value 0 if its operations were performed without errors, thus allowing to complete the opening of our library, and any other value to signal an error. In this case the library won't open.
__UserLibCleanup(), instead, doesn't return any value, so it must be declared as void.

Both functions get only one argument, a pointer to the library to be opened (or closed) in the A6 register.

To complete our example, therefore, we can write an additional module, with the name, say, UserLib.c, just like the following:

#include "exec/libraries.h"
#include "proto/exec.h"

struct Library *GfxBase;
struct Library *IntuitionBase;

int __asm __UserLibInit(register __a6 struct Library *libbase)
{
   GfxBase = OpenLibrary("graphics.library",0L);
   IntuitionBase = OpenLibrary("intuition.library",0L);

   if (GfxBase && IntuitionBase)
   {
      return (0);
   }
   else
   {
      if (GfxBase) CloseLibrary(GfxBase);
      if (IntuitionBase) CloseLibrary(IntuitionBase);
      return (1);
   }
}

void __asm __UserLibCleanup(register __a6 struct Library *libbase)
{
   if (GfxBase) CloseLibrary(GfxBase);
   if (IntuitionBase) CloseLibrary(IntuitionBase);
}

Let's now compile this module exactly the same way as before, then link it again this way:

SLink FROM LIB:libent.o LIB:libinit.o UserLib.o Test.o
      TO LIBS:test.library LIB LIB:sc.lib LIBPREFIX _PR_
      LIBFD test_lib.fd

Now our new library will actually work as intended.

If the library you want to create doesn't need to allocate specific global resources you can avoid to redefine __UserLibInit() and __UserLibCleanup(), as a standard version of them is already present in the libinit.o object module, which is always linked to the library.

In this case you don't need to write or to compile additional modules such as UserLib.c (exactly like in the first version of the example).

As already mentioned, you can also use the LIBVERSION and LIBREVISION keywords with SLink to assign version and revision numbers to the library, as well as the LIBID keyword to assign a version string.
For instance:

SLink [...] LIBVERSION 43 LIBREVISION 114 LIBID "test 43.114 (25.9.96)"

The final step is to obtain the include file with the #pragma directives using the FD2Pragma utility:

FD2Pragma test_lib.fd test_pragmas.h

Once that is done, you can at last use the library in the usual way in your programs, for example:

[...]

#include "exec/libraries.h"
#include "proto/exec.h"
#include "test_pragmas.h"

struct Library *TestBase;

main(void)
{
   [...]

   TestBase = OpenLibrary("test.library",0L);

   if (TestBase)
   {
      printf("%d * 3 = %d\n",4,Three(4));

      [...]

      CloseLibrary(TestBase);
   }
}

[...]





A last note: by specifying the libinit.o module in the linker call (like in our example) you obtain a library having only one copy of its global data, shared among all programs which opened the library.
To create a library generating a new copy of its global data for each calling task every time it is opened, you must specify the libinitr.o module instead (note the 'r').




The above is only a summary review of the operations you need to perform to create a new library; more detailed information can be found, of course, in the original SAS/C 6.x documentation, in the manual as well as in two on-disk library examples, in the directories extras/examples/samplelib and extras/examples/reslib.


Main Page


    Written by: Massimo Tantignone  e-mail: tanti@intercom.it
                Via Campagnoli, 4
                28100 Novara
                ITALY