Copyright (c) Hyperion Entertainment and contributors.

Difference between revisions of "Libraries and Devices"

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
 
(2 intermediate revisions by the same user not shown)
Line 6: Line 6:
   
 
For an introduction to some basic concepts see [[Exec_Libraries|Exec Libraries]].
 
For an introduction to some basic concepts see [[Exec_Libraries|Exec Libraries]].
 
==== Using Devices ====
 
 
Using devices is almost exactly the same as under AmigaOS 3.9, only that you call the methods of IExec. Since you usually don't call any of the device vectors directly, there is no apparent change to using a device. You still create an IORequest and message port, open the Device with the IExec->OpenDevice() call and call IExec->DoIO() to perform tasks.
 
   
 
==== The Self Pointer and the APICALL Tag ====
 
==== The Self Pointer and the APICALL Tag ====
Line 126: Line 122:
 
ULONG FubarFunc(ULONG param1, ULONG param2, struct FubarBase *libBase)
 
ULONG FubarFunc(ULONG param1, ULONG param2, struct FubarBase *libBase)
 
{
 
{
libBase->FubarCalled++;
+
libBase->FubarCalled++;
ULONG x = param1 * libBase->FubarFactor / param2;
+
ULONG x = param1 * libBase->FubarFactor / param2;
 
return x;
 
return x;
 
}
 
}
Line 163: Line 159:
   
 
This example assumes that your data layout for the interface looks like the struct FubarMainData structure.
 
This example assumes that your data layout for the interface looks like the struct FubarMainData structure.
 
==== Library Initialization ====
 
 
Initialization via init tables is no longer supported starting with AmigaOS 4. To initialize a library you need to provide a ROMTAG structure like with previous versions of the OS, although the interpretation of some of the fields are a bit different. Most notably, the rt_Flags field must have the RTF_NATIVE flag set to indicate that the code in this library is native PowerPC and not 68k. If the RTF_AUTOINIT flag is not set, the rt_Init field is assumed to point to a function that is called up on initialization (depending on the RTF_NATIVE flag, it might be called under emulator control).
 
 
If the RTF_AUTOINIT flag is set, then the rt_Init must point to a taglist that will be passed to a function - the function again depends on RTF_NATIVE ROMTAGs without the RTF_NATIVE flag will be handled identically to the old way - their rt_Init field points to a init table and is passed to MakeLibrary.
 
 
Native libraries expect to find a taglist at rt_Init that is passed to exec.library/CreateLibrary. This creates a new-style PowerPC-native library with an optional 68k interface part (We'll cover the 68k part later).
 
 
A typical taglist for this looks somewhat like this:
 
 
<syntaxhighlight>
 
struct TagItem fubar_libCreateTags[] =
 
{
 
{CLT_DataSize, sizeof(struct FubarBase)},
 
{CLT_InitFunc, (uint32)fubar_libInit},
 
{CLT_Interfaces, (uint32)fubar_libInterfaces},
 
{TAG_END, 0}
 
};
 
</syntaxhighlight>
 
 
In this case, the first tag specifies the size of the base structure, the second tag specifies the libinit function (we'll look at that in a minute) and the last tag specifies the interfaces array. The interfaces array is a null-terminated array of pointers to tag lists for the exec.library/MakeInterface call. Typically you will have at least two interfaces here, the library manager interface and an exported interface (which will most likely be "main"). The layout of the library manager interface has already been discussed, we are going to cover the methods in a few moments.
 
 
The interfaces array might look something like this:
 
 
<syntaxhighlight>
 
uint32 fubar_libInterfaces[] =
 
{
 
(uint32)fubar_managerTags,
 
(uint32)fubar_mainTags,
 
0
 
};
 
</syntaxhighlight>
 
 
The first would be the taglist for creating the manager interface, the second for the main interface. We will only cover the manager interface here, since the main interface looks exactly the same, with different functions only. Note that both start with the four default vectors.
 
 
For out library manager interface, the tag list looks like this:
 
 
<syntaxhighlight>
 
struct TagItem foobar_managerTags[] =
 
{
 
{MIT_Name, (uint32)"__library"},
 
{MIT_VectorTable, (uint32)foobar_manager_vectors},
 
{MIT_Version, 1},
 
{TAG_END, 0}
 
};
 
</syntaxhighlight>
 
 
Here, the name is fixed (in any other interface the name could be chosen freely) and the version number is also fixed to "1". you may use different version numbers in "normal" interfaces, but an interface of this type must be present.
 
 
The foobar_manager_vectors variable is the vector table. This is simply a null-terminated array of methods, in the order in which they appear in the interface. For the library manager interface, we need to have the methods as outlined earlier in this document. The table should look somewhat like this:
 
 
<syntaxhighlight>
 
void *fubar_manager_vectors[] =
 
{
 
(void *)generic_Obtain,
 
(void *)generic_Release,
 
(void *)0,
 
(void *)0,
 
// In general, start the "normal" methods here
 
(void *)fubar_libOpen,
 
(void *)fubar_libClose,
 
(void *)fubar_libExpunge,
 
(void *)0,
 
(void *)-1,
 
};
 
</syntaxhighlight>
 
 
As per usual the interface starts with the default methods. We use the generic obtain and release methods, and leave both expunge and clone unimplemented (if you leave expunge unimplemented, the interface will simply be freed).
 
 
The following three vectors are the same as with classic libraries, only that they are assumed to be native code as opposed to 68k code. We'll list sample implementations a bit further down.
 
 
The last (in this case unimplemented) vector is the GetInterface call. With all probability you will leave this empty for just about everything. Exec already offers a GetInterface function that should be sufficient for just about every purpose. Only very special cases require this vector, so we will skip its discussion for the purpose of this document.
 
 
The idltool provides a good sample implementation of the three above-mentioned library manager methods and should be referenced for more details.
 
 
We now need to look at the libInit function. This was specified using the CLT_InitFunc tag in the ROMTAG-anchored taglist for CreateLibrary. The function is responsible for setting up one-shot resource stuff and things that are not handled in the open code. Generally, you will almost always be able to use the generic implementation provided by the idltool.
 
   
 
== Handling 68K ==
 
== Handling 68K ==

Latest revision as of 22:36, 22 April 2013

Introduction

Overview

This guide is most specifically directed at programmers wanting to convert library code to AmigaOS 4.x. A (hopefully) comprehensive explanation of the process of converting a library will be presented.

For an introduction to some basic concepts see Exec Libraries.

The Self Pointer and the APICALL Tag

If you look at the standard vector declarations above, you will notice that their first parameter is always a "struct Interface *Self", but we do not specify that Self parameter in the calls. Instead, all method function pointers have a tag APICALL added. That tag identifies them as interface function pointers.

Through this special function attribute, the gcc compiler for AmigaOS 4 will recognize a construct of the form

IExec->CloseLibrary(libBase);

and implicitly (not by preprocessor magic, but through the code generator) transform this into

IExec->CloseLibrary(IExec, libBase);

This saves the user the hassle of writing this, but of course this only works on the AmigaOS 4 compiler. Obviously, any compiler adapted for AmigaOS 4 must implement this mechanism.

Migrating a Library from AmigaOS 3.x

Introduction

Migrating a library from AmigaOS 3.x may or may not be trivial. It all depends on the internals of the library. For a library function itself, the most obvious change is that the library base will not be passed along, but the interface pointer. The interface pointer, however, contains the library base pointer, so if that is required it can still be gotten from the interface. Also, the interface itself may carry along private or instance data.

Interface Definition Files

The first thing to do when starting a new library or migrating an existing one is to write the IDL file . The IDL file is what used to be the FD file in the old system, with a few substantial differences. First of all, the file format is now XML, using a special "library" DTD. The file should have the same base name as the library with the suffix ".xml" added, e.g. exec.xml, expansion.xml and so on. Each IDL file should start like this:

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE library SYSTEM "library.dtd">
<library name="expansion" basename="ExpansionBase">
...
</library>

This declares an XML file and includes the dtd. The third line (<library...>) defines the document's contents, in this case a single library. The "name" attribute specifies the name of the library. A ".library" is implicitly added to this name. The second attribute (basename) specifies the global name of the library base and the name of the base structure (i.e. struct ExpansionBase * in this case). Note that the library base pointer need not be global at all for a function to be called - only the interface pointer is used for that.

The <library> element may contain any number of <include> elements. <include> elements specify include files that need to be present to define all types that are used within the library. The files <exec/interfaces.h> and <exec/types.h> are implicitly added. The name of the include file must be given as contents to the <include> element, and only one such file is allowed per element:

<include>expansion/pci.h</include>

After the <include> element, you should start listing all library interfaces. There can be any number of interfaces. Interfaces are specified using the <interface> element:

<interface name="pci" version="1" flags="protected" struct="PCIIFace" prefix="_pci_">

The following attributes exist:

  • name (required) The name of the interface. This name will be used by an application to request the interface.
  • version (default: 1) The version of the interface. Interface versions start at 1. Multiple interfaces with the same name may be defined if and only if they have different version numbers. Once an interface is published, it must not be changed unless the interface version number is incremented.
  • flags (default: none) The flags to set for this interface. This may be "protected", "private", or "none", with obvious mappings to the interface flags.
  • struct (required) The name of the structure that holds the interface, automatically prefixed by "struct". The example above specifies the struct name to be "PCIIFace", that means that any interface acquired from expansion library by the name of "pci" will have the layout of "struct PCIIFace".
  • prefix (default: "_impl_") The common prefix for all interface functions. It is highly recommended that all interface functions get a command prefix, since some function names might be ambiguous, like Obtain, Release, Expunge and Clone. This prefix is used for the automatic library creation process.

The <interface> element may contain any number of <method> elements. <method> elements describe the methods that an interface provides. Contrary to FD files the method description also contains the return types and parameter types. The following lists the four standard interface methods, which must be first in every interface:

<method name="Obtain" result="uint32"></method>
<method name="Release" result="uint32"></method>
<method name="Expunge" result="void" status="unimplemented"></method>
<method name="Clone" result="struct Interface *" status="unimplemented"></method>

These methods don't have any other parameter but the Self pointer, which is implicit and need not be named. The "name" attribute defines the name of that method, the name under which the method is called when used. Together with the prefix specified in the interface, it forms the name of the function (see the testlib example). The "result" attribute describes what the method returns, which may be "void" if the function does not return anything, or a type like "uint32", or even a complex type like "struct Interface *". Finally, the "status" attribute used by some methods (which defaults to "implemented") specifies if the method actually exists or not. If it doesn't exist, set this to "unimplemented". This will "reserve" a slot in the interface's jump table, but fill it with NULL. Obviously, calling such a function results in Violent Program Termination.

After these initial methods, you need to add a <method> element for each method in the interface. Methods without parameters can be written as above, or in a single tag, as per XML specifications:

<method name="Obtain" result="uint32"/>

If a method has arguments (the most common case), the arguments must be listed as <arg> elements:

<method name="AllocResource" result="struct PCIResourceRange *">
 <arg name="ResType" type="uint32"/>
 <arg name="NumBytes" type="uint32"/>
</method>
 
<method name="FreeResource" result="void">
 <arg name="Resource" type="struct PCIResourceRange *"/>
</method>

These two declarations would result in the following two method prototypes:

struct PCIResourceRange *AllocResource(struct PCIIFace *Self,
 uint32 ResType, uint32 NumBytes);
void FreeResource(struct PCIIFace *Self, struct PCIResource *Resource);

<arg> elements must not have any content, and hence should always be written in the one-tag way.

Remember to close the <interface> element by a trailing </interface>. As was said before, any number of <interface> elements may follow.

Once you have written the interface description, the "idltool" program must be used to generate the required header files. Running idltool with the "--all" option generates all available targets.

Migrating the Source Code

As has been said before, library calls in AmigaOS 4 are arranged into interfaces. There are four interface methods that every interface has. Usually you can use generic versions of these. The idltool will output a complete skeleton for you which makes it much easier to get going.

All functions need to be modified in such a way that the LibBase parameter (if present) is removed, and the new Self parameter needs to be added. For example, consider this function (AmigaOS 3.9 style) from a fictitious library "fubar.library":

ULONG FubarFunc(ULONG param1, ULONG param2, struct FubarBase *libBase)
{
 libBase->FubarCalled++;
 ULONG x = param1 * libBase->FubarFactor / param2;
 return x;
}

The modifications required mostly depend on a few design choices we have to make. The "FubarCalled" variable is part of the library base in the example above but that need not be the case for AmigaOS 4 - we can move this field either to the interface private data or leave it in the library base.

We assume that our interface for the library is called "FubarMainIFace". You may chose any name but it is recommended (though not enforced) that they start with the library name and end with "IFace".

uint32 FubarFunc(struct FubarMainIFace *Self, uint32 param1, uint32 param2)
{

So far, things that have changed is the types are modernized and the removal of the libBase parameter and the addition of the interface pointer. What follows depends on the choice of placement of the FubarCalled and FoobarFactor fields. Let's first assume that we left them in the library base, so we need a pointer to the library. That pointer is always stored in the interface:

struct FubarBase *libBase = (struct FubarBase *)Self->Data.LibBase;
 libBase->FubarCalled++;
 uint32 x = param1 * libBase->FubarFactor / param2;
 return x;
}

Now, if we decided to move these fields into the interface itself, things look a bit different. Generally if you think that data is specific to an interface, and hence specific to a task context, put the fields into the interface. Otherwise leave them in the base. Migrated libraries will probably want to keep them in the base structure, but just in case this is the way to access the data from the interface:

struct FubarMainData *ifData =
 (struct FubarMainData *)((uint32)Self - Self->Data.NegativeSize);
 ifData->FubarCalled++;
 uint32 x = param1 * ifData->FubarFactor / param2;
 return x;
}

This example assumes that your data layout for the interface looks like the struct FubarMainData structure.

Handling 68K

Overview

There are two possible scenarios from where a library might be called: From native code, or from 68k code. The native code calls will invariably go via an interface; the 68k calls will always be base-relative.

This means that for a library to be native-callable, at least one interface must exist. Since this is only some sort of switchboard, we will call these "proxy" interfaces: the interface just redirects calls through the emulator.

This also means that if a native library needs to be callable from 68k, it must have a jump table. Of course, for a 68k program to call a native library, this program must know the library; it is highly unlikely that a 68k program will call the PCI interface of expansion.library directly (i.e. not through an emulation layer) because it will most likely not know that expansion.library offers anything but the "normal" functions.

Native Interfaces to 68K Libraries

A 68k library that needs to be native-callable needs an interface. That interface may be created through two different mechanisms. For a disk-based library, the loader will scan the directory for a file that contains this interface, to be loaded with the library. This file may or may not contain additional code that replaces some of the library vectors.

For resident libraries, this looks a bit different. Resident libraries must have the interface already created in memory. Since these libraries are most likely part of the system itself, these libraries should be treated very much like a native library.

Assume for example that graphics.library would remain 68k. In order to call it from native code, it needs to have an interface (preferably "main"). This is achieved by inserting a ROMTAG with a priority of one lower than the actual library, which constructs the interface and stores it as an interface of Exec, under the name "graphics.interface". When a program attempts to obtain an interface from a non-AmigaOS 4 library, Exec's GetInterface function constructs this name from the library name and returns the pointer to this "proxy" interface.

Using idltool

The utility program idltool can be used to generate libraries as well, in a very convenient manner. All you have to do is provide the XML interface description file and invoke idltool with the command line

idltool -k <interfacefile>.xml

This will create a directory <interfacefile>_files in your current directory that contains a complete skeleton library with a Makefile and a README with additional instructions. Simply typing "make" should build the library. For more information, refer to the documentation that comes with idltool.