Copyright (c) Hyperion Entertainment and contributors.

The Right Tool for the Job (Shared Objects)

From AmigaOS Documentation Wiki
Revision as of 21:56, 16 October 2012 by Steven Solie (talk | contribs)
Jump to navigation Jump to search

For any task, using the right tool for the job is always a crucial matter. This applies to driving a nail into a wall as much as developing software. And while nobody would ever try to use a glass bottle for the nail, the tools of the trade of the software developer are a bit more abstract (and sometimes, more brittle too).

Shared != Shared

On AmigaOS the word “shared” is used in two major contexts: Shared Library, and Shared Object. Both are tools for sharing code between applications. However, they have very different methods for doing this, and with that comes a very different approach to using them.

Let’s first look at what they are.

Shared Libraries

Since the early days of AmigaOS, shared libraries have been a means of sharing code and, to a certain degree, data between multiple users. A shared library is, essentially, a structure in memory called the Library Base, and one or more jump tables to functions that are to be shared. Since Version 4.0 of the OS, these jump tables are called Interface, and although their use differs slightly from their setup in AmigaOS 3.9 and earlier, the principles are the same. A program intending to use a library has to do two steps in order to perform any calls into that library:

  • It has to open the library by calling Exec’s OpenLibrary call.
  • It has to obtain at least one interface from the library by calling GetInterface.

The latter step was not needed on the classic AmigaOS 3.x, but has opened up a host of new possibilities on AmigaOS 4.0 (we’ll talk more about that in a later article).

Interfaces, like the classic AmigaOS 3.x jump tables, are a collection of function pointers in a structure. Calling a function in an interface usually involves knowing the offset of that function. We typically call a function like this:

struct Library *library = IExec->OpenLibrary("foo.library", 0);

The variable IExec contains the interface. The OpenLibrary call is a member of the interface. During compile time, the compiler will calculate the offset of the member and generate appropriate code for that. The code will load the IExec interface pointer into a register, load the CTR register with the address at the specified offset, and branch into the routine using the bctrl mnemonic.

The process relies on a few factors. First of all, it requires to have the library open and have the interface ready. It also relies on the fact that an interface, once written, will at most be extended at the end. It will never be possible to remove functions from the interface (at least it will always have to have a `dummy' entry) nor will it be possible to re-order functions.

Data access is done via the library’s base pointer. The implementer might chose to store user-accessible data within the library base and document (at least part) of it for public access. Since this is a compile-time decision to make, again, the organization of data, just like the organization of functions, must not change once it has been published (unless the library base data is private).