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 |
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 } |
| 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?
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; 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 ); } |
Last updated on
Saturday, December 17, 2005 12:41