Copyright (c) Hyperion Entertainment and contributors.
How to create an AmigaOS 4 library
This simple tutorial is based on the creation of my first library.
Big thanks to Alexandre Balaban, Tony Wyatt and Andy Broad for their advice and explanations.
Contents
Create a descriptive xml file
To generate the skeleton of the library we first need to create an xml file describing it. You may have a look in SDK:Include/interfaces to get inspiration.
The header
At the beginning you describe the type of document. For my sample I got inspired by one generated by fdtrans
<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE library SYSTEM "library.dtd">
The library tag
The all library description is contained in the library tag. First you need to enter the name of the library, the name of its base structure and the name of the library file
<library name="sample" basename="SampleBase" openname="sample.library">
The include tags
You use these tags to specify the include files required for the declaration of your methods signature. If your methods use custom types, you should add your own new include
<include>exec/types.h</include> <include>test/sample.h</include> In that case, don't forget to place your include in SDK:local/common/include
The interface tag
This tag defines an interface of your library. You should at least declare the "main" interface.
<interface name="main" version="1.0" struct="SampleIFace" prefix="_sample_" asmprefix="ISample" global="ISample">
Interface methods
In the interface section you add a method tag for each method of your library you want to publish.
Note: you must provide at least Obtain, Release, Expunge and Clone
<method name="Obtain" result="uint32"/> <method name="Release" result="uint32"/> <method name="Expunge" result="void" status="unimplemented"/> <method name="Clone" result="struct SampleIFace *"/>
method properties
- name: the name of the method to publish
- result: the data type returne by your method
- status: allows you to specify that a method is not implemented
arg tags
Each argument of a method must be declared with an arg tag. You specify the name and the type properties
<method name="Addition" result="myInt"> <arg name="value1" type="myInt"/> <arg name="value2" type="myInt"/> </method>
Once you're finished with the description your file should look like this:
<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE library SYSTEM "library.dtd"> <library name="sample" basename="SampleBase" openname="sample.library"> <include>exec/types.h</include> <include>test/sample.h</include> <interface name="main" version="1.0" struct="SampleIFace" prefix="_sample_" asmprefix="ISample" global="ISample"> <method name="Obtain" result="uint32"/> <method name="Release" result="uint32"/> <method name="Expunge" result="void" status="unimplemented"/> <method name="Clone" result="struct SampleIFace *"/> <method name="Addition" result="myInt"> <arg name="value1" type="myInt"/> <arg name="value2" type="myInt"/> </method> </interface> </library>
Generate the skeleton with IDLTool
Open a shell in the drawer where your XML file resides and launch IDLTool so that it generates all you need
idltool -a sample.xml
copy the includes at the right place
Now you have an include drawer which you'll can put in SDK:local/common/include Note: you may delay this and use the local includes while debugging your library.
> copy include/#? SDK:local/common/include all proto (Rép.) sample.h..copié. inline4 (Rép.) sample.h..copié. interfaces (Rép.) sample.i..copié. sample.h..copié.
If you have an include required for the signature of your methods, you can add it too.
#ifndef TEST_SAMPLE_H #define TEST_SAMPLE_H typedef int myInt; #endif // TEST_SAMPLE_H
> makedir SDK:local/common/include/test > copy test/#? SDK:local/common/include/test sample.h..copié.
add the revision info
Go into the <library>_files drawer, sample_files in my example. Now use bumprev to generate revision files.
> bumprev 1.1 sample.library bumprev: Creating new file "sample.library_rev.rev". bumprev: Bumped "sample.library" to version 1.1.
bumprev doesn't accept '0' values, but you may modify the *_rev file afterward.
tune init.c
IDLTool has generated source files in the <library>_files drawer. First you have a init.c file and a drawer for each interface with a source file for each method declared in your xml file, except the methods tagged as 'unimplemented'. Your first task is to edit init.c and define your library's base structure. You will find a struct Library definition, rename it and enhanced it with private data if needed
struct Library { struct Library libNode; BPTR segList; /* If you need more data fields, add them here */ };
struct SampleBase { struct Library libNode; BPTR segList; /* If you need more data fields, add them here */ };
If you enhanced it, it's interesting to move it in a new header so that you can declare it in other source files.
In that case, don't forget to add the include directive in init.c.
#include <exec/exec.h> #include <proto/exec.h> #include <dos/dos.h> #include <exec/types.h> #include <test/sample.h> #include <proto/sample.h> #include <stdarg.h> #include "samplebase.h
samplebase.h
#ifndef SAMPLEBASE_H #define SAMPLEBASE_H struct SampleBase { struct Library libNode; BPTR segList; /* If you need more data fields, add them here */ struct Library *UtilityBase; struct UtilityIFace *IUtility; }; #endif // SAMPLEBASE_H
Now you must edit init.c to replace the (struct Library *) casts to your library type, (struct SampleBase *) in my case.
To help you find the needed places, just use make:
> make gcc -Wall -O3 -c -o init.o init.c init.c: In function 'libOpen': init.c:66: error: 'struct Library' has no member named 'libNode' init.c: In function 'libExpunge': init.c:94: error: 'struct Library' has no member named 'libNode' init.c:96: error: 'struct Library' has no member named 'segList' init.c:105: error: 'struct Library' has no member named 'libNode' init.c: In function 'libInit': init.c:116: error: 'struct Library' has no member named 'libNode' init.c:117: error: 'struct Library' has no member named 'libNode' init.c:118: error: 'struct Library' has no member named 'libNode' init.c:119: error: 'struct Library' has no member named 'libNode' init.c:120: error: 'struct Library' has no member named 'libNode' init.c:121: error: 'struct Library' has no member named 'libNode' init.c:122: error: 'struct Library' has no member named 'libNode' init.c:124: error: 'struct Library' has no member named 'segList' make: *** [init.o] Error 1
Now you have the functions which use a libBase variable typed as a (struct Library *) instead of your own base, just correct the declarations and check with make.
> make gcc -Wall -O3 -c -o init.o init.c gcc -nostartfiles -o sample.library main/Obtain.o main/Release.o main/Clone.o main/Addition.o init.o
Once make succeeds, you get a brand new library
> list sample.library sample.library 6510 ----rwed Aujourd'hui 18:13:21
get the interfaces you need
Now that your library skeleton is validated, you have to implement your methods, but you may need acces to other libraries interfaces for it. When you compile a regular program, the startup code adds global variables for some libraries (i.e. IExec), but in the case of a library, you don't have these, so it's up to you to do it.
Except for Newlib, it's a good practice to store your opened libraries and interfaces in you libbase by extended it. On the other hand, using global references allows your code to look like regular code.
IExec
In libInit() you get a (struct Interface *) which you can keep for your library use. No need to call Obtain() on it, as Exec isn't going away. You may copy it to a global variable or to your libbase.
global IExec
At the beginning of init.c add a global definition:
struct ExecIFace *IExec = NULL;
In libInit(), comment out the "struct ExecIFace *IExec UNUSED" line and copy the exec argument to your variable
IExec = (struct ExecIFace *)exec;
To access it from your sources, just include proto/exec.h as it contains a declaration for such a global interface. In your code you can use method calls like
IExec->OpenLibrary()
IExec libbase property
Extend your libbase structure to add a property to it
#ifndef SAMPLEBASE_H #define SAMPLEBASE_H struct SampleBase { struct Library libNode; BPTR segList; /* If you need more data fields, add them here */ struct ExecIFace *IExec; }; #endif // SAMPLEBASE_H
In libInit(), copy the exec argument to your variable
libBase->IExec = (struct ExecIFace *)exec;
To access it from your sources, just include your "samplebase.h". In your code you can get the interface from the libBase through the interface pointer of your library passed to your methods
struct SampleIFace * _sample_Clone(struct SampleIFace *Self) { ... ((struct SampleBase *)(Self->Data.LibBase))->IExec->AllocVecTags()
INewlib
If you use functions from libc and compile with newlib (default), the compiler will complain about an undefined INewlib. That's because your library does not have the startup code which opens newlib.library and gets an interface from it. The solution is almost the same as for a global IExec. At the beginning of init.c add global definitions:
struct Library *NewlibBase = NULL; struct Interface *INewlib = NULL;
In libInit(), open the library and get a "main" interface where the skeleton says it
/* Add additional init code here if you need it. */ NewlibBase = IExec->OpenLibrary("newlib.library", 53); if(!NewlibBase) { CleanLibContext("Can't open V53 newlib.library"); return NULL; } INewlib = (struct Interface *)IExec->GetInterface(NewlibBase, "main", 1, NULL); if(!INewlib) { CleanLibContext("Can't get newlib main interface"); return NULL; }
CleanLibContext() may look like this:
void CleanLibContext(const char *msg) { IExec->DebugPrintF("%s\n", msg); if(NewlibBase) { if(INewlib) { IExec->DropInterface(INewlib); INewlib = NULL; } IExec->CloseLibrary(NewlibBase); NewlibBase = NULL; } }
Now you can enjoy libc functions.
NB: Do not make a call to a libc function before getting your interface, it's bad !
In libExpunge() you must clean up your resources after the dedicated comment:
/* Undo what the init code did */ CleanLibContext("");
other libraries
Now you should have a pretty clear idea of how to do it for whatever library.
- define pointers for the library and the interface,
- open the library and get the interface in libInit()
- if you get an error while opening the library or getting the interface, clean up your resources and return NULL in libInit()
- in libExpunge() close everything opened
Implement your library's methods
IDLTool has generated a source file for each of the declared methods.
You just fill them with your code.
This is a profundly trivial example
myInt _sample_Addition(struct SampleIFace *Self, myInt value1, myInt value2) { return (value1 + value2); }
Test your library
Once your library is built, it's time to check each method with a simple test program.
#include <proto/exec.h> #include <proto/sample.h> #include <stdio.h> int main(int argc, char *argv[]) { myInt val1, val2, res; struct Library *SampleBase = NULL; struct SampleIFace *ISample = NULL; SampleBase = (struct Library *)IExec->OpenLibrary("sample.library", 0); if(!SampleBase) return -1; ISample = (struct SampleIFace *)IExec->GetInterface(SampleBase, "main", 1, NULL); if(!ISample) { IExec->CloseLibrary(SampleBase); return -2; } val1 = 123; val2 = 321; res = ISample->Addition(val1, val2); printf("%ld + %ld = %ld\n", val1, val2, res); IExec->DropInterface((struct Interface *)ISample); IExec->CloseLibrary(SampleBase); return 0; }
Build it, copy your library in the same drawer (except if you already put it in Libs:) and test it
9.test> gcc -g -o tst_sample tst_sample.c 9.test> tst_sample 123 + 321 = 444
Et voila !
Hope this article will help you create much usefull libraries.