Copyright (c) 2012-2016 Hyperion Entertainment and contributors.

Migration Guide

From AmigaOS Documentation Wiki
Jump to: navigation, search

Introduction

In spite of being largely compatible with the classic API, there are a few rules that a programmer should be aware of when porting programs from the 68k-based AmigaOS 3.x to the new PowerPC-based AmigaOS 4. This document tries to outline the process and also point to a few potential traps and issues.

Source Code Level

Include Files

Include files used to be a problematic issue under OS 3.x because there where a lot of different compilers, and most of them tended to use their own naming scheme and glue to bind to the operating system. The well-known SAS/C and StormC compiler systems used #pragma's for this purpose while gcc used inline functions or #define directives AmigaOS 4 at the moment supports two compiler systems (GNU GCC and the vbcc package) and both use the same scheme for include files.

To use a library under AmigaOS 4, you simply include its proto file, in addition to any other file that may be required. For example, to use Intuition Windows and Screens, you would add the following to your program:

#include <intuition/intuition.h>
#include <intuition/screens.h>
#include <proto/intuition.h>

The proto file includes the required headers, which might depend on the compiler you are using. Just including the proto file will ensure maximum compatibility between different compilers.

A few preprocessor symbols influence the way that the system bindings are included. Table 1 summarizes these symbols. They are typically defined in a Makefile, although they may as well be put into #define directives. Don't worry if you don't understand everything in that table; things will become clearer further down this document.

Symbol Meaning
__USE_INLINE__ This symbol makes the protofile include an inline4 file as well. An inline4 file contains preprocessor macros for functions that resemble the classic way of calling system functions (i.e. functions as opposed to methods). Primarily intended for backward compatibility with older source code, or general compatibility.
__NOLIBBASE__ Inhibits the definition of the library base associated with the proto file. Usually a proto file declares an extern symbol for the library base.
__USE_BASETYPE__ If __NOLIBBASE__ is not defined, this symbol specifies whether the library base is declared with its "real" type (for example struct IntuitionBase for Intuition) or with the abstract base type for the class of resource (struct Library or struct Device). Only library implementors should use this symbol; normally, all library base structures are private and should not be examined. The default behavior is to declare all library bases as struct Library and all device bases as struct Device.
__NOGLOBALIFACE__ Proto files for libraries that declare static global interfaces (like all "classic" libraries) normally also declare the interface pointer inside their proto file, unless this symbol is defined. Normally, you would only use this if you want to store the interface pointer elsewhere, for example in a library base.

Table 1: Preprocessor symbols that control the proto file

Speaking of preprocessor symbols, the compiler defines a preprocessor symbol when targeting AmigaOS 4.x: __amigaos4__ and you can use this symbol for optional compilation of code.

Library Bases and Interface Pointers

Library bases should not normally be declared inside a source file. The proto file usually declares the required library base itself. If you want to do it anyway, you should define the preprocessor symbol __NOLIBBASE__ to prevent the proto file from doing it.

Contrary to the old system, AmigaOS 4 has a slightly different way of calling library functions. The old system used to call a library function by making a relative jump into a jump table located directly in front of the library base. AmigaOS 4 keeps these jump tables for backwards compatibility only - only 68k functions are still called this way. The new OS now keeps jump tables in a separate pointer called the "Interface Pointer", or short "interface". Basically an interface is a structure with a bit of housekeeping information and a lot of inline function pointers. A library may export more than one interface pointer (it usually exports at least two).

Almost all libraries export an interface by the name of "main". The exact type of this interface depends on the library. For example, exec.library will export a main interface of type "struct ExecIFace *" (Note that it would theoretically be possible for another library to export an interface of type "struct ExecIFace *").

Like the library bases formerly used to call library functions, by convention a global variable is used to make the ExecIFace pointer available to all functions in your program. The name of that variable is constructed by prepending the library base name ("Exec" in this example) with a capital I. Thus, the global struct ExecIFace * would be called IExec. Note that this is simply a convention introduced by the proto files. You do not need to even use a global interface pointer, as much as you don't need to use a global library pointer (neither now in AmigaOS 4, nor in AmigaOS 3.9 or earlier).

Type Consistency

The PowerPC architectural manual frequently uses the word "word" to signify a 32 bit quantity. However in classic AmigaOS, the types WORD and UWORD meant a 16 bit signed and unsigned quantity. Likewise, an 8 bit quantity used to be called BYTE or UBYTE and 32 bit "words" where called LONG and ULONG. No 64 bit quantity existed. To clean up the naming of base types, these types have all been deprecated for AmigaOS 4. The new types are consistently called int or uint followed by the number of bits, for example uint32 signifies a 32 bit unsigned quantity. Likewise, the typing has been extended to include a 64 bit quantity. Table 2 lists the new types and the "classic" types they replace. Note that the old types are still available through preprocessor defines, but should not be used anymore in new code.

New Type Old Type Significance
int8 BYTE 8 bit signed
uint8 UBYTE 8 bit unsigned integer
int16 WORD 16 bit signed
uint16 UWORD 16 bit unsigned integer
int32 LONG 32 bit signed
uint32 ULONG 32 bit unsigned integer
int64 None existed 64 bit signed
uint64 None existed 64 bit unsigned integer
float32 FLOAT 32 bit single-precision floating point number
float64 DOUBLE 64 bit double-precision floating point number

Table 2: New Base Types

Programming Considerations

Library Initialization using libauto

System libraries may be opened automatically using the libauto link library. libauto can automatically open a number of system libraries (see libauto Libraries for a list of these libraries) and also set up their interface pointers. If you include the proto file in your program and link with libauto, the appropriate library will be all set up. For example, including <proto/intuition.h> will set up IntuitionBase and IIntuition automatically so that they are all set up when your program enters the main() function. The following example program opens a simple window, waits a bit then closes it again:

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
 
int main()
{
 struct Window *win = IIntuition->OpenWindowTags(NULL,
  WA_Title, "Example Window",
  WA_Width, 640,
  WA_Height, 480,
  TAG_END);
 
 IDOS->Delay(150);
 
 IIntuition->CloseWindow(win);
 
 return 0;
}

For the time being, ignore the way that functions are written (IIntuition->OpenWindowTags as opposed to a simple OpenWindowTags, we'll cover that later).

Save this program as windowtest.c and compile it with:

gcc -o windowtest windowtest.c -lauto

Manual Library Initialization

Libraries can also be initialized by hand. As with the old system, the function to do this is exec.library's OpenLibrary function. However, as we have hinted above, the library base is not the only requirement for using a library - you need to retrieve the interface pointer (or rather, all interface pointers you need) from the library to make use of it. On the classic system, opening for example intuition.library looked like this:

IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 36);
if (IntuitionBase == NULL) PanicExit("Cannot open intuition library");
 
// ...
struct Window *win = OpenWindowTags(NULL, ...);
// ...
 
CloseWindow(win);

Under AmigaOS 4, this will look like this:

IntuitionBase = IExec->OpenLibrary("intuition.library", 50);
IIntuition = (struct IntuitionIFace *)IExec->GetInterface(
 IntuitionBase, "main", 1, NULL);
 
if (IIntuition == NULL) {
 PanicExit("Cannot obtain main interface from Intuition");
}
 
// ...
struct Window *win = IIntuition->OpenWindowTags(NULL, ...);
// ...
 
IExec->DropInterface((struct Interface *)IIntuition);
IExec->CloseLibrary(IntuitionBase);

Changed passes have been marked in bold. As you can see, there are some obvious changes:

  1. All functions are prefixed by either IExec-> or IIntuition->.
  2. An additional variable, IIntuition is initialized.
  3. IntuitionBase is now a struct Library and no longer needs a typecast.

The new variable is called the "interface pointer". Note that when you call GetInterface, you need to pass in the library base from which you want to retrieve the interface pointer, its name ("main" in this case, as in almost all classic libraries) and a version number (1 is the base version). The last parameter is a pointer to a tag list; for defined tag items please consult the autodoc.

The interface pointer is needed to actually call the functions of a library (remember that the "original" jump table only contains 68k pointers). Thus, to call an Intuition function, you write "IIntution->OpenWindowTags" instead of a simple "OpenWindowTags". Note that there are ways around this; as we said in the introduction, using the preprocessor symbol __USE_INLINE__ will automatically include a file that contains a macro definition of the following type:

#define OpenWindowTags(...) \
 IIntuition->OpenWindowTags(__VA_ARGS__)

This will both ensure compatibility with old source code, as well as allowing you to maintain different versions of your software.

Opening Devices

Opening a device also works largely the same as under OS 3.x. If a device doesn't export its own function table, like serial device, then there is no additional step required. If, on the other hand, the device does export its own function table, you need to retrieve the interface pointer just as you would retrieve the interface pointer from a library. For example, timer device is usually opened like this (error checking removed for clarity):

struct MsgPort *TimerMP = IExec->AllocSysObject(ASOT_PORT, NULL);
 
struct TimeRequest *TimerIO = IExec->AllocSysObjectTags(AOST_IOREQUEST,
 ASOIOR_Size, sizeof(struct TimeRequest),
 ASOIOR_ReplyPort, TimerMP,
 TAG_END);
 
IExec->OpenDevice(TIMERNAME, UNIT_MICROHZ, TimerIO, 0);
 
struct Library *TimerBase = (struct Library *)TimerIO->tr_node.io_Device;
 
struct TimerIFace *ITimer = (struct TimerIFace *)
 IExec->GetInterface(TimerBase, "main", 1, NULL);
 
ITimer->GetSysTime(&amp;tv);
 
if (!IExec->CheckIO(TimerIO))
 IExec->AbortIO(TimerIO);
 
IExec->WaitIO(TimerIO);
IExec->DropInterface((struct Interface *)ITimer);
IExec->CloseDevice(TimerIO);
IExec->FreeSysObject(ASOT_IOREQUEST, TimerIO);
IExec->FreeSysObject(ASOT_PORT, TimerMP);

As you can see, except for the initialization of the interface pointer, everything is the same. The AllocSysObject functions at the top are new to Exec V50, and fulfill the same function as AllocDosObject in DOS. Consult the autodoc for more info. Also note the timer.device structures have been renamed.

Calling Library Functions

We've basically covered this before already, but we'll review it here for completeness. A libraries interface pointer can be used to call the libraries functions. Thus, if you want to call an Exec function, you need to explicitly call the "method" from the interface.

You might wonder why we chose this path instead of simply providing stub functions. Well, for one thing if you do not like this kind of calling mechanism, you can completely isolate yourself from it by using the inline4 macro files. These "simulate" the use of "normal" functions, however, they come with the same issues as the preprocessor based inlines of the classic gcc, namely that - as preprocessor macros - they aren't aware of name spaces and scopes, and are blindly replaced on a textual basis. Thus, if you have for example a C++ class list that contains a member function AddHead, the preprocessor will most likely complain that you used a macro with the wrong number of arguments.

Likewise, it means that the name of a function needs to be unique across the whole system. The result are longer function names with redundant naming.

Finally, the interfaces allow for other things. For example, expansion library uses interfaces in a sort of object-oriented way - every PCI device on the bus is represented by a separate interface. That means that instead of repeating the PCI bus, device and function ID's as parameters every time, you can write something like:

a = MyDevice->ReadConfigByte(PCI_INTERRUPT_LINE);
MyDevice->WriteConfigByte(PCI_INTERRUPT_LINE, a & 0x0F);

The following chapter goes a bit deeper into the matter; you may skip it if you want, since ported classic programs will seldom need to make use of interfaces other than the basic initialization and cleanup tasks.

Interface Basics

Interfaces have a usage count. That count is implicitly incremented when you call GetInterface and decremented again on DropInterface. You can explicitly do that using the Obtain() and Release() method that all interfaces implement. You must do this if you intend to use the interface you obtained somewhere outside the context of your task. For example, consider an image loader library that returns a picture in the form of an interface. Suppose that this interface contains a Print() method that you can use to send the image to a printer. Quite naturally, this will take its time, so the program launches a subtask that takes care of that. The subtask would look something like that (some error handling removed for clarity):

void print_task(struct PictureIFace *IPicture)
{
 IPicture->Print(some_printer_description);
 IPicture->Release();
}
 
// Somewhere else...
struct PictureIFace *IPicture = ILoader->Load("DH0:Backdrop.jpg");
 
// Pass the picture to the print spooler.
IPicture->Obtain(); // Add a reference so that it will not vanish too soon
 
// Create the spooler task. CreateTaskTags is new for V50.
// Note how you can now pass arguments to the created task.
if (IExec->CreateTaskTags("Printer Spooler",
 SPOOLER_PRIORITY, print_task, SPOOLER_STACK_SIZE,
 AT_Param, IPicture,
 TAG_END) == NULL)
{
 // Task creation failed.
 IPicture->Release(); // Release the unused reference.
 
 do_some_error_stuff();
}
 
// Spooler is running, we don't need our copy anymore.
IPicture->Release();

What is happening here? The Load() method would create the IPicture interface, thus giving us a fully usable IPicture interface with one reference. Since we want to pass it to a subtask, we do add another reference to the internal counter (increasing it to two) and pass it on down to the spooler task. After that task is running, we simply mark our copy as unused by calling Release() on it (we're now back at one again).

When the spooler is done, it will call Release() itself again, thus bringing the counter to zero. If the usage counter of an interface ever reaches zero, this indicates to the system that the interface is done with, and will trigger the interface's destruction.

Calling 68K Functions

Some system code as well as some application code might still be 68k based. Usually an application programmer doesn't need to worry about this, though as most of the details are handled internally by the system. Typical places where PowerPC to 68k transitions take place are:

  1. 68k libraries
  2. 68k hook functions
  3. 68k interrupts

68k Library Entries

For this discussion we assume that we have a disk based library called "foo.library" that is written in 68k code. Previously we have said that library calls always go through an interface - in most cases called "main" - but obviously the writer of the 68k library was unaware of this mechanism.

In order to present a unified calling mechanism to the programmer, 68k libraries are called through the same interfaces as a PowerPC library would. Since the original library doesn't provide this interface, a disk-resident stub library will do that. In our case, this library would be called "foo.l.main". The 'l' comes from the first letter of the resource type ("library" in this case), and the "main" is the name of the interface it provides. Thus, the "foo.l.main" stub will provide the interface "main" for "foo.library". If you call ObtainInterface() on a 68k library, the system will automatically scan its library search path for the file "foo.l.main" and if found, will try to open it and obtain the interface.

While this may look strange at first sight, it offers the possibility to migrate "foo.library" to PowerPC later on without the need to even recompile the application(s) using it. Furthermore, for a transition the "foo.l.main" interface may contain a few "real" PowerPC functions - normally it is composed from stub functions that call the 68k emulator.

The downside is that (at the time of writing) the "foo.l.main" library will not be created automatically. There are tools available in the SDK to build it from an SFD file, though. This process is described in a different document.

68k Hook Functions

Hook functions in general are completely transparent when it comes to PowerPC vs. 68k issues. You must not call a hook function directly, though - doing this used to be legal in the pre-4.0 era, but will result in an ISI exception for AmigaOS 4.

Hook functions must be called through Utility's CallHookPkt function. Invoking a hook this way will make sure that the appropriate measures are met - either by invoking the emulator, or by a real jump into the code.

68k Interrupts

As with hook functions, interrupts are handled completely transparently. When an interrupt is added to the system, a few checks are done and the interrupt's ln_Type field may be modified. This is done to ensure minimum latency when the interrupt occurs. The consequence is that you may not change the is_Code field of an interrupt node without removing and re-adding it to the system first.

Calling The Emulator Directly

In rare cases you might want to directly call the 68k emulator to execute 68k code. Exec provides the function Emulate for this purpose. Emulate can be called with an implicit register mapping and an arbitrary address. For more information on Emulate please refer to its autodoc.

Other Programming Considerations

General Hook Functions

Hook functions used to be called with a specific mapping of 68k registers. Quite naturally, there are no 68k registers on a PowerPC machine, so the programmer needs to be a bit careful when writing a hook function.

CallHookPkt, or its vararg version CallHook, will always adhere to the PowerPC SysV ABI, and therefore the order of the parameters in the called function is important (it was always to be considered bad style to rearrange the parameters of a hook function, but possible through the explicit declaration of register mappings).

A hook function should always look like this:

uint32 HookFunction(struct Hook *hook, APTR object, APTR message);

The first argument, hook, points to the hook with which this function was invoked. The second paramter, objec, is a generic 'target' for the oeration, and depends on the context that this hook is being called in. The final argument, message, is a pointer to a message package whose layout also depends on the hook context.

Hook functions need to have the VARARGS68K tag. The next chapter will describe what that means.

Varargs

Although frequently used, the following code is highly system- and compiler-dependent:

void somefunc(int messageid, ...)
{
 char *message = (char *)(&messageid + 1);
 if (messageid == some_id)
 {
 int x = *(int *)message; message += sizeof(int);
 int y = *(int *)message; message += sizeof(int);
 // ...

This code assumes that arguments are put on the stack in sequential order, which happens to be true by chance but cannot be counted on. On AmigaOS 4.0 onwards, the system uses the PowerPC SystemV ABI, which doesn't allow for such tricks. ANSI-C defines a specific mechanism for variable number of arguments functions, namely va_start(), va_end() and va_arg(). . Under all normal circumstances, these must be used on AmigaOS 4.

The only legal way to express the above code is:

#include <stdarg.h>
void somefunc(int messageid, ...)
{
 va_list ap;
 va_start(ap, messageid);
 if (messageid == some_id)
 {
 uint16 x = va_arg(ap, uint16);
 uint16 y = va_arg(ap, uint16);
 // ...
 }
 
 va_end(ap);
}

There is one exception to this rule, though. Under certain circumstances it is necessary to be able to use these stack varargs because of compatibility. The AmigaOS 4 compilers must support a means to specify this as a special linkage parameter for a function. The SDK include file amiga_compiler.h encapsulates the details in a common macro VARARGS68K. A function declared with this tag is handled differently by the compiler, so that the calling method outlined at the beginning of this chapter works. The tag may be used both to declare a function within your program as well as declaring a function outside your program. The former is required for example for hook functions, while the latter may be required for legacy code.

Such a function still needs to access the parameters in a special way. The first argument (messageid in this example) still is passed in a register according to the ABI specs. The following fragment shows how to handle this case.

#include <stdarg.h>
 
void somefunc(int messageid, ...) VARARGS68K;
 
void somefunc(int messageid, ...)
{
 va_list ap;
 va_startlinear(ap, messageid);
 
 char *message = va_getlinearva(ap, char *);
 
 if (messageid == some_id)
 {
 uint16 x = *(uint16 *)message; message += 4;
 uint16 y = *(uint16 *)message; message += 4;
 // ...
 }
 
 va_end(ap);
}

Note that parameters passed in this way are always longword-aligned. On the 68k this used to be different depending on the compiler - another reason why you should avoid this kind of calling mechanism.

Memory

The MEMF_PUBLIC flag

The memory subsystem of AmigaOS 4 has seen a radical overhaul. Although the API stays mostly the same, there are a few things to look out for when porting old code, as well as when writing new code.

A general rule of thumb is: Don't use it. There is usually no reason why you should be using MEMF_PUBLIC memory. On AmigaOS 3.9 and earlier you had to allocate e.g. Messages and Message Ports in public memory; this is no longer needed under AmigaOS 4 - instead use the MEMF_SHARED flag. The MEMF_SHARED flag shares a lot of the properties that its public counterpart has, but is much more "friendly" to the system.

For more information about MEMF_PUBLIC see Obsolete Exec Memory Allocation.

Visible Changes in the Memory Subsystem

Most of the changes done to the memory system under AmigaOS 4 is done "under the hood" and invisible to the application programmer. However, a few changes may affect the programmer, and therefore need to be taken care of.

First of all, memory is virtualized. That means that any access to unallocated memory will invariably result in an abnormal termination of your program. Like the Enforcer in earlier versions of the system, the AmigaOS 4 memory system does not tolerate any such access. Contrary to the Enforcer, however, such illegal accesses are fatal. AmigaOS 4 software must not access illegal memory areas.

Memory will, as a rule, go away at the very moment that it is freed. Being inside a forbid state does not allow you to access memory after freeing it.

Also, virtualized memory means that the physical address where memory is located does not necessarily match the virtual address that you are using. While that doesn't have any impact in normal operation, it is something that device driver writers will have to take care of - there are calls in Exec that map physical to virtual and vice versa. In addition, memory that appears continuous to your application (which uses virtual addresses) need not be continuous in physical memory.

In the past some clever programmers where scanning Exec's memory lists, in spite of a comment that clearly marked them as "private". This will no longer be tolerated in AmigaOS. In fact, chances are that the memory lists are uninitialized, since the traditional memory allocation schemes are in the process of being phased out at the time of writing in favor of a new and much faster memory allocation.

As we have already mentioned, there are some new memory flags for application programmers. MEMF_SHARED is one of them, the semantics of which have already been described above. Another one of these is MEMF_EXECUTABLE. Normally, executable code may not be placed into an arbitrary section of memory. If you intend to generate code dynamically, you must use MEMF_EXECUTABLE to allocate the memory. Also note that the code section of your program is write protected, so self-modifying is not possible unless you allocate a piece of MEMF_EXECUTABLE memory and generate your code there.

Please refer to Exec Memory Allocation for more information.

Known Incompatibilities

Due to the migration to PowerPC and other changes in the system itself, there are a few known incompatibilities with old software and/or source code that may cause trouble. This is true both for porting software to run on AmigaOS 4 natively as well as binary 68k legacy programs. The following list tries to outline the known issues and tries to list workarounds.

Most of these issues are caused by inappropriate programming, for example exploiting undocumented or internal features. In general, if you strictly adhere to the programming guidelines your program should work unmodified, but some things that where tolerated under OS 3.9 and earlier will no longer work.

  • Assembler programmers should NOT simply check the zero flag if the function is documented to return 0 or non-null. For example, if you open a device it is NOT sufficient to check the zero flag to find out if the open was successful or not. This assumes that the return value is set up at the very end of the function and that no operation had any influence on it; furthermore, since the emulator "only" calls native code and passes the return value to your 68k function, the flag bits are not affected. This was never supposed to work, and will no longer work. A simple "cmp.l #0,d0" or "tst.l d0" will be sufficient.
  • Access to any unmapped/unallocated memory fill most likely result in a fatal crash. It is not OK to "temporarily store" results somewhere because your application "happens to know" that the area is free. Likewise, NULL pointer access will invariably result in a crash. The user will be able to click "continue" in the Grim Reaper window that will pop up, though, but the result will be undefined.
  • It has never been documented where and how (if at all) AllocVec stores the size of the block it has allocated. Therefore, it has always been illegal to try to access the four bytes before the actual allocation to find the size of the block. Code that will use this "feature" will no longer work, since AmigaOS 4's AllocVec does not necessarily put anything there.
  • Public data does not belong on the stack. If you want to send a message, allocate it with AllocSysObjectTags() using the ASOT_MESSAGE type. The following code is broken:
void foo(void)
{
 struct Message msg;
 // ...
 IExec->PutMsg(port, &msg);
}
  • Locale library uses new country/language names. All language names are represented by their English name because it eliminates the need for special characters that the file system may not support or that require a special font to display.
  • Do not copy font flags. Font flags carry a specific meaning that may or may not be specific to a certain font. For example, setting the FSF_ANTIALIASED flag on a bitmap font may cause unpredictable behavior.
  • It never was, and never will be, legal to access memory after you have freed it. No matter if you are in Forbid() state or not, a program MUST NOT access memory after it called FreeMem() or any other function that frees memory. Doing so will invariably cause a crash of your program. It is not OK to assume that memory will stay available up to the next Permit(). Freeing memory will most likely trigger immediate un-mapping of associated memory pages, meaning that any further access will result in a Data Storage (DSI) exception. The same holds true for executable memory that is freed due to a FreeVec or UnLoadSeg. The next instruction to be executed will no longer be there because the memory has vanished, resulting in an ISI exception.

libauto Libraries

The following global variables are automatically handled by the current libauto. Note that the entries in bold are automatically supplied by your C library's startup code and are always available (clib2 does not provide UtilityBase and IUtility):

Library Interface(s)
AmigaGuideBase IAmigaGuide
ApplicationBase IApplication, IPrefsObjects
ARexxBase IARexx
AslBase IAsl
BevelBase IBevel
BitMapBase IBitMap
SocketBase ISocket
ButtonBase IButton
CheckBoxBase ICheckBox
ChooserBase IChooser
ClickTabBase IClickTab
ColorWheelBase IColorWheel
CxBase ICommodities
DataTypesBase IDataTypes
DateBrowserBase IDateBrowser
DiskfontBase IDiskfont
DOSBase IDOS
DrawListBase IDrawList
ElfBase IElf
ExecBase IExec, IDebug
ExpansionBase IExpansion
FillerBase IFiller
FuelGaugeBase IFuelGauge
GadToolsBase IGadTools
GetColorBase IGetColor
GetFileBase IGetFile
GetFontBase IGetFont
GetScreenModeBase IGetScreenMode
GlyphBase IGlyph
GfxBase IGraphics
IconBase IIcon
IFFParseBase IIFFParse
InputBase IInput
IntegerBase IInteger
IntuitionBase IIntuition
KeymapBase IKeymap
LabelBase ILabel
LayersBase ILayers
LayoutBase ILayout
ListBrowserBase IListBrowser
LocaleBase ILocale
LowLevelBase ILowLevel
MiniGLBase IMiniGL
PaletteBase IPalette
PenMapBase IPenMap
P96Base IP96
PictureBase IPicture
PopupMenuBase IPopupMenu
RadioButtonBase IRadioButton
RequesterBase IRequester
RexxSysBase IRexxSys
ScreenBlanker IScreenBlanker
ScrollerBase IScroller
SketchBoardBase ISketchBoard
SliderBase ISlider
SpaceBase ISpace
SpeedBarBase ISpeedBar
StringBase IString
TextClipBase ITextClip
TextEditorBase ITextEditor
TimerBase ITimer
TimesyncBase ITimesync
TimezoneBase ITimezone
UserGroupBase IUserGroup
UtilityBase IUtility
VirtualBase IVirtual
WorkbenchBase IWorkbench
WindowBase IWindow
xadMasterBase IxadMaster