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.
__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:
Parameter | Comment |
---|---|
LIBCODE
| Mandatory |
SAVEDS
| Alternatively you can use the __saveds keyword
|
Parameter | Comment |
---|---|
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),
One()
, Two()
and Three()
, all of them with only one argument.
The idea is as follows:
One()
simply returns its argument's value.
Two()
attempts to open the "topaz 8" font with
graphics.library's OpenFont()
function, and returns the double of
its argument's value if it succeeds, and zero otherwise.
Three()
flashes the screen by calling intuition.library's
DisplayBeep()
function, and returns three times its argument's value.
#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); } } [...] |
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. libinitr.o
module
instead (note the 'r
').
extras/examples/samplelib
and
extras/examples/reslib
.
Written by: Massimo Tantignone e-mail: tanti@intercom.it Via Campagnoli, 4 28100 Novara ITALY