Before we begin with the simple requester example I'd like to talk for a moment about the recent release of Amiga OS 4. After over two and a half years Hyperion-Entertainment has finally released the Amiga OS 4 pre-release. This is great news for the Amiga community and is a wonderful opportunity for us as programmers. The only trouble is that, in order to migrate the Amiga OS to the PowerPC processor, certain changes had to be made. These changes are good for the Amiga development community, but they do make things slightly more confusing for the beginner. I'll take a moment to discuss these changes and how they affect the programs that we've been creating so that those of you who have OS 4 can experiment with making the necessary changes to these examples. The main changes in OS 4 deal with the way libraries are opened and the way library functions are called. These changes make use of pointers, which is not a big deal since we've already discussed them in lesson 2.
Libraries are still opened by a call to OpenLibrary()which returns a pointer to the opened library. In earlier versions of the OS it was common practice to store this pointer in a special structure like struct GfxBase or struct IntuitionBase. This was accomplished by casting the struct Library * returned by OpenLibrary() to the required type. This was common practice which allowed the programmer access to some internal data about the library but was not strictly required to use the library's functions:
struct GfxBase *GfxBase = NULL;
GfxBase = (struct GfxBase *)OpenLibrary( "graphics.library", 0L );
In OS 4 there is no longer any need to create a special structure variable to hold the pointer. All libraries can now use a simple struct Library to hold their information, therefore no cast is needed. This means the above code would be written in OS 4 as:
struct Library *GfxBase = NULL;
GfxBase = IExec->OpenLibrary( "graphics.library", 0L );
Notice that GfxBase is now a struct Library *, not a struct GfxBase * and there is no (struct GfxBase *) cast needed. But what on earth is IExec-> doing before the call to OpenLibrary()? This is a strange syntax to find in C and definitely new to Amiga programming. Lets think about this a little bit. In lesson 2 we talked about pointers and learned that if you have a pointer to a structure and want to access a member of that structure you can use the arrow operator "->". So, we can reason that IExec is a pointer to some type of structure, and that OpenLibrary() is a member of that structure.
(Note: Those of you who are familiar with C++ classes may not think this syntax is odd since C++ classes can contain functions--called methods in object oriented programming--and calling a method from a class pointer uses the above syntax. But remember that we are using C, not C++, and structures in C cannot have functions as members. As a general rule: classes in C++ can contain functions and data, but structures in both C and C++ can contain only data.
For completeness, a reader recently pointed out to me that it is actually possible for functions to be part of a structure by defining them within the structure:
This does not appear to work on my version of GCC (2.95.3) and it may be compiler dependant. I have never seen this style used in practice and I would think it could get rather clumsey to include long function definitions directly in a structure definition, but I suppose it be useful in some instances. However, I will refrain from using this technique in these tutorials for the sake of clairity, and compatibility.)
Until now we haven't discussed functions being members of structures. Typically functions cannot be members of structures directly. But pointers can be members of structures, and a pointer can be set with the address of a function. A pointer that contains the address of a function is naturally called a function pointer. Here's an example:
03 struct MyStruct
05 int (*Func)(int); /* A function pointer. */
08 int Double(int number)
10 return number * 2; /* Multiply by 2. */
13 int main(void)
15 int my_number;
16 struct MyStruct my_structure; /* The actual structure. */
18 my_structure.Func = &Double; /* Load the structure member with the address of the Double function. */
19 my_number = my_structure.Func( 10 ); /* Call the function by referencing the structure member. */
21 printf( "my_number is %d\n", my_number ); /* Print the result. */
23 return 0;
02 #include <stdio.h>
04 struct MyStruct
06 int (*Func)(int); /* A function pointer. */
09 int Double(int number)
11 return number * 2; /* Multiply by 2. */
14 int main(void)
16 int my_number;
17 struct MyStruct *p_my_structure = NULL; /* A pointer to the structure. */
19 /* Allocate memory for the structure. */
20 p_my_structure = (struct MyStruct *)malloc( sizeof( struct MyStruct ) );
22 if ( !p_my_structure )
24 printf( "Out of memory!\n" );
25 return 1;
28 p_my_structure->Func = &Double; /* Load the structure member with the address of the Double function. */
29 my_number = p_my_structure->Func( 10 ); /* Call the function by referencing the structure member. */
31 printf( "my_number is %d\n", my_number ); /* Print the result. */
33 free( p_my_structure ); /* Free the allocated memory. */
35 return 0;
That's how functions can be called from structure pointers. This is an extremely powerful concept...but what does it all mean?
In classic Amiga programming (i.e. before OS 4), a library is divided into three major parts. The first section is called the function offset table, which is basically a list of assembly jump statements with pointers to the start of each function in the library.
The second section is the library base structure. The address of this structure is returned by OpenLibrary() and is always at least sizeof(struct Library) in length (This is at least 34 bytes) and additional information my be appended to this structure if necessary. This is why we cast the library base from a struct Library * to struct GfxBase * when opening the graphics.library prior to OS 4. If we did not make this cast, we could not easily access the additional data after the library structure. If we open the graphics/gfxbase.h header file in a text editor we can see that struct GfxBase begins with a struct Library. Also note that this is not a pointer to a struct Library, but the entire struct Library becomes part of the struct GfxBase. This ensures that we can use the library functions even without making the cast.
The third section contains the code for each function. The positions of the
jump instructions in the offset table never change. When a change is made to
a function in the library, the jump table is simply updated with the new starting
position of that function. The key to this method is the library base pointer
returned by OpenLibrary().
As sated above, this pointer always points to the beginning of a struct
Library. This means that we can access the offset table by using negative
offsets from the library pointer. These negative offsets are called Library
Vector Offsets or LVOs and are each 6 bytes in length.
The figure to the left illustrates how this process works. Notice that if we make changes to Function 1 then Function 2 will most likely not begin at address +108 from the library base pointer. In this case, all we must do is change the jump instruction in address -18 to point to the new starting address of Function 2. We would also need to change the jump instruction for Function N (-12) and all the following functions.
The beauty of this design is that we only need to know the LVO values to use the library. And since the LVO values never change, we simply jump the the correct LVO address for the function we wish to execute and the program flow is automatically redirected to the correct function.
The only down side of this system is that it uses two jump instructions--one of which isn't really necessary. In OS 4 this extra jump instruction is eliminated. This is accomplished by using a new structure called an Interface.
A struct Interface is similar to a function offset table, but it contains pointers directly to each of the functions in the library. This allows us to call the function directly and eliminates the second jump instruction. IExec->OpenLibrary( "graphics.library", 0L ) is an example of a function call using an interface. In this case IExec is a struct Interface * that points to an interface containing all the functions in the exec.library.A library can have more than one interface, but all OS 4 libraries must have at least one interface which is called the "main" interface. The ability to have more than one interface could be used to limit access to certain functions in the library. This is a concept that is useful in object oriented programming, but at this time it is unclear how this will be used in future Amiga libraries. In any case, we can obtain a pointer to an interface by calling the IExec->GetInterface() function.
| struct Library
*GraphicsBase = NULL;
struct Interface *IGraphics = NULL;
/* Open the graphics.library. */
GraphicsBase = IExec->OpenLibrary( "graphics.library", 50L );
IGraphics = IExec->GetInterface( GraphicsBase, "main", 1, NULL );
We can use the library functions now.
Remember to preface all function calls
with the interface pointer: "IGraphics->".
/* Close the graphics.library. */
IExec->DropInterface( IGraphics );
IExec->CloseLibrary( GraphicsBase );
Last updated on
Saturday, December 17, 2005 12:41