Copyright (c) Hyperion Entertainment and contributors.

How to create an AmigaOS 4 library

From AmigaOS Documentation Wiki
Revision as of 22:05, 21 April 2016 by Ölrick Lefebvre (talk | contribs) (Sample tutor on how to create an OS4 library from scratch)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

How to create an OS4 library

This simple tutorial is based on the creation of my first library.

Big thanks to Alexandre Balaban, TonyW and Andy Broad Blues for their advices and explanations.


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.