< Lesson 4: Draw  |  Index  |  Lesson 6: Simple Requester >


Lesson 5:  OS 4 Libraries


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:


struct MyStruct
{
   int a;
   void my_function(void)
   {
      printf( "I'm in my_function!\n" );
   };

   float b;
};

int main(void)
{
   struct MyStruct test;

   test.my_function();

   return 0;
}


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:


01    #include <stdio.h>
02
03    struct MyStruct
04    {
05        int (*Func)(int);                         /* A function pointer. */
06    };
07
08    int Double(int number)
09    {
10        return number * 2;                        /* Multiply by 2. */
11    }
12
13    int main(void)
14    {
15        int my_number;
16        struct MyStruct my_structure;             /* The actual structure. */
17
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. */
20
21        printf( "my_number is %d\n", my_number ); /* Print the result. */
22
23        return 0;
24    }

In the above example we first defined MyStruct. The function pointer is declared on line 5. It looks a little bit like a function prototype, only the function name is in parentheses and there is an asterisk before the name. But all the information you'd expect to see in a function is there. The return type, int, begins the line. The name of the function is next, but it's in a special format that lets the compiler know that we are declaring a function pointer and not a pointer to a regular variable. The way we declare a function pointer is to surround the name of the function with parentheses and place an asterisk immediately before the name. It's also useful to understand that the function name, in this context, is not necessarily the name of the function which is pointed to. It can be any name you wish, and since it's a member of a structure it can even be a name that you may have previously used as a function name. Notice that following the function name in parentheses is another set of parentheses which contain the types of arguments, just as in a function prototype. The arguments in this list must match the arguments in the function that is pointed to. So, in this case, the function pointer can point to any function that takes an int as an argument and also returns an int.

On line 8 we declare the real function Double() that takes an int as an argument and also returns an int. This is the same prototype as the prototype of our function pointer. Pretty sneaky, eh?  Notice that on line 18 we give the Func member of my_structure (i.e. the function pointer) the address of Double(). Then in line 19 we use Func as a function call, passing it a value and assigning the return value to my_number. This has the exact same effect of calling the Double() function.

Below is the same example that we just discussed, except we use a struct MyStruct pointer:

01    #include <stdlib.h>
02    #include <stdio.h>
03
04    struct MyStruct
05    {
06        int (*Func)(int);                         /* A function pointer. */
07    };
08
09    int Double(int number)
10    {
11        return number * 2;                        /* Multiply by 2. */
12    }
13
14    int main(void)
15    {
16        int my_number;
17        struct MyStruct *p_my_structure = NULL;   /* A pointer to the structure. */
18
19        /* Allocate memory for the structure. */
20        p_my_structure = (struct MyStruct *)malloc( sizeof( struct MyStruct ) );
21
22        if ( !p_my_structure )
23        {
24            printf( "Out of memory!\n" );
25            return 1;
26        }
27
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. */
30
31        printf( "my_number is %d\n", my_number ); /* Print the result. */
32
33        free( p_my_structure );                   /* Free the allocated memory. */
34
35        return 0;
36    }

That's how functions can be called from structure pointers. This is an extremely powerful concept...but what does it all mean?

Old LibrariesIn 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.

By putting all of this knowledge together we can use the following code to open and use a library in OS 4 (in this case the graphics.library, as an example):

struct Library *GraphicsBase = NULL;
struct Interface *IGraphics = NULL;

int main(void)
{
   /* 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 );
}

We've covered some really heavy topics in this quick tutorial and unless you use OS 4 you don't really need to know most of this information right now, so don't feel bad if it's not perfectly clear. That being said, the concept of function pointers as members of structures is very powerful and well worth the effort to understand.

< Lesson 4: Draw  |  Index  |  Lesson 6: Simple Requester >

If you find any errors or have any questions or comments please e-mail me.

Last updated on Saturday, December 17, 2005 12:41