AROS ​​Vision

Modern and free Amiga-Compatible Experience on Amiga and PC

Article 18: Programming the Amiga How Applications 

Communicate with AmigaOS In the previous articles, we examined the Amiga from several different perspectives. 

We have explored:
the Motorola 68000 CPU
Chip RAM and Fast RAM
the custom chipset
Exec and multitasking
libraries and devices
AmigaDOS
filesystems and handlers
DMA
interrupts

Now we can bring many of these concepts together and look at the machine from the perspective of an application programmer. How does an Amiga program actually use the operating system? How does it allocate memory? How does it open a window? How does it receive mouse and keyboard events? How does it read files? And how does all of this interact with Exec? The answer reveals one of the most interesting characteristics of AmigaOS: An Amiga application does not simply run on top of the operating system. It participates in a system of libraries, tasks, messages, signals and shared resources. 

1. Two Ways to Program the Amiga 

There are broadly two approaches to classic Amiga programming. Operating-system programming 

The application uses AmigaOS services.
Application
     |
     v
AmigaOS
     |
     v
Hardware 

Typical examples include: 
Workbench applications
utilities
editors
productivity software
communication programs

Direct hardware programming 

The program accesses the custom-chip hardware more directly.
Program
   |
   v
Custom-chip registers
   |
   v
Hardware

This approach became particularly common in: 
games
demos
multimedia software

The two approaches can also be combined, but doing so requires care. 

2. Why Use the Operating System? 

Suppose you want to create a window. 

You could theoretically try to manage everything yourself: 
display timing
mouse input
clipping
window borders
screen coordinates
keyboard input
drawing
memory

But AmigaOS already provides these services. Instead the application can use:
intuition.library for user-interface functionality and:
graphics.library for graphics operations. 

This dramatically reduces the amount of code required. 

3. An Amiga Application Is Usually Part of the Multitasking System 

An ordinary Amiga application runs under Exec's multitasking system. 

Conceptually:
                 Exec Scheduler
                      |
          +-----------+-----------+
          |           |           |
       Workbench    Editor      Your Program
          |           |           |
        Task        Task        Task/Process 

Your program is not normally the only software executing. Exec continuously coordinates the runnable tasks. This has an important consequence: A well-behaved application must cooperate with the rest of the system. 

4. Tasks and Processes Revisited 

As we learned earlier, Exec understands:
Task while AmigaDOS adds the higher-level:
Process A DOS process includes additional information such as: current directory
file handles
standard input
standard output
DOS-related state

A program launched through AmigaDOS will therefore commonly execute as a process. 

Conceptually:
Exec Task
    +
DOS information
    =
DOS Process 

5. Program Startup 

An Amiga program can be launched in several ways. 

Two important examples are:
Shell and:
Workbench 

These environments start applications differently. 

From the Shell:
Shell
  |
  | command line
  v
Program From Workbench:
Workbench
    |
    | startup message
    v
Program 

A program supporting both environments needs to handle the appropriate startup mechanism. 

6. Opening Libraries 

One of the first things many traditional Amiga applications do is open the libraries they require. 

For example:
OpenLibrary() 

Suppose the application needs Intuition. 

Conceptually:
IntuitionBase =
    OpenLibrary("intuition.library", 0); 

The application receives the library base if the library is available. The library base provides access to the library interface.
 
7. Always Check the Result 

Applications should never assume that a requested resource is available. 

Conceptually:
LibraryBase = OpenLibrary(...);

if (LibraryBase == NULL)
{
    /* handle error */
} 

This principle applies throughout Amiga programming. 

Resources may fail to become available because of: 
insufficient memory
missing libraries
incompatible versions
unavailable devices
unavailable files

Robust software checks these conditions. 

8. Library Versions 

Programs can request a minimum library version. 

For example:
Application requires version 39
              |
              v
        OpenLibrary()
              |
       +------+------+
       |             |
    V39 or newer    older
       |             |
      OK            fail 

This allows an application to ensure that the APIs it depends on actually exist. It also contributed to compatibility between different versions of AmigaOS. 

9. Allocating Memory 

Applications frequently need memory for: 
data structures
buffers
graphics
text
file data
temporary calculations
Exec provides memory-management functions. 

A classic example is:
AllocMem() 

As we saw in Article 13, the application can specify memory requirements. 

For example:
MEMF_CHIP or:
MEMF_FAST or:
MEMF_CLEAR 

10. Choosing the Correct Memory 

Suppose the program needs a buffer for calculations. 

Fast RAM may be appropriate.
CPU
 |
 v
Fast RAM 

But suppose it needs a bitmap that the classic custom chipset must access through DMA. Then Chip RAM may be required.

CPU
 |
 v
Chip RAM
 ^
 |
DMA 

Therefore memory allocation on an Amiga is not only about: How many bytes are required? It can also be about: Who needs to access those bytes? 

11. Creating a Window 

Suppose our application wants to create an Intuition window. 

Conceptually:
Program
   |
   | request
   v
intuition.library
   |
   v
Window 

The application supplies information describing the desired window. 

This may include: 
position
width
height
title
gadgets
event types
screen
Intuition then manages the window within the graphical environment. 

12. The Application Does Not Manage Everything 

Once the window exists, the application does not need to reinvent the complete graphical user interface. Intuition helps manage things such as: 
window activation
gadget interaction
menus
window movement
resizing
user-interface events

This is precisely what an operating-system abstraction should provide. The application concentrates on its own functionality. 

13. Event-Driven Programming 

GUI programs cannot simply perform one fixed sequence of commands and then exit. They must respond to the user. 

For example:
Mouse click
Keyboard input
Window close
Menu selection
Gadget pressed
Window resized 

The program therefore spends much of its time waiting for events. This leads to an important programming style: Event-driven programming 

14. The Event Loop 

A simplified GUI program might work like this:
Open libraries
      |
      v
Open window
      |
      v
+-------------------+
| Wait for event    |
+-------------------+
      |
      v
Process event
      |
      v
Continue?
 /       </span>
Yes       No
 |         |
 +---------+--> cleanup 

This loop may continue until the user closes the application. 

15. Intuition Messages 

Intuition communicates many user-interface events through messages. T

hese are commonly known as:
IntuiMessages 

For example, an application may receive messages indicating: gadget activity
mouse buttons
raw key events
window refresh
menu selections
window close requests

This brings us directly back to Exec's message system. 

16. Message Ports 

Again A window can be associated with a message port. 

Conceptually:
User
 |
 v
Intuition
 |
 v
IntuiMessage
 |
 v
Message Port
 |
 v
Application 

The application receives events from the port. This illustrates how the Amiga's GUI is built upon the same fundamental communication mechanisms used elsewhere in the operating system. 

17. Waiting Efficiently 

A badly designed event loop might repeatedly check:
Any message?
Any message?
Any message?
Any message? This wastes CPU time. Instead, the application can wait. 

Conceptually:
Application
     |
     v
Wait()
     |
     | asleep
     |
Event occurs
     |
     v
Signal
     |
     v
Application wakes 
#
While the program waits, Exec can run other tasks. This is cooperative use of the multitasking environment. 18. Signals and Message Ports When a message arrives at a port, the corresponding task can be signaled. 

The relationship looks roughly like this:
Event
  |
  v
Message
  |
  v
Port
  |
  v
Signal
  |
  v
Task wakes 

The task then reads the message from the port. Again, several pieces of Exec's architecture work together. 

19. Processing a Message 

A simplified program structure might look conceptually like:
Wait(signal);

message = GetMsg(port);

if (message)
{
    /* inspect message */
} The program examines the message type and decides what action to perform. For example:
CLOSEWINDOW
     |
     v
Terminate event loop or:
GADGETUP
    |
    v
Perform gadget action

20. Replying to Messages 

One important feature of Exec messages is ownership. When a program has finished processing certain messages, it must return them appropriately. 

For Intuition messages, this normally involves:
ReplyMsg() 

The principle is important: Receiving a message does not necessarily mean that the receiver owns the underlying message memory permanently. Once the information has been processed, the message is returned. This avoids unnecessary copying and fits the Amiga's memory-efficient design. 

21. Message Passing Is Everywhere 

At this point we have seen messages in many parts of AmigaOS:
Intuition
Devices
DOS handlers
Processes
Applications
Inter-task communication 

This is not accidental. Message passing is one of the architectural foundations of AmigaOS. Instead of every component directly manipulating every other component, messages allow independent tasks to communicate. 

22. Using AmigaDOS 

Suppose our program needs to open a file. It can use functions provided by:
dos.library 

Conceptually:
file = Open("Work:Data/Test", MODE_OLDFILE); The application receives a handle representing the open file. 

It can then perform operations such as:
Read()
Write()
Seek()
Close() 

The application does not need to know whether the file resides on: 
a floppy
a hard disk
a RAM disk
another filesystem implementation
AmigaDOS provides the abstraction. 

23. Several OS Layers 

May Be Involved Opening a file may look simple from the application's perspective:
Open("Work:Data") But underneath:
Application
     |
     v
dos.library
     |
     v
AmigaDOS
     |
     v
Filesystem handler
     |
     v
Device driver
     |
     v
Hardware 

This is precisely why operating-system APIs are valuable. The application deals with a logical operation rather than physical implementation details. 

24. Using Devices 

Sometimes an application needs lower-level I/O. For this, it may use an Exec device. The general structure involves an I/O request.
Application
     |
     v
I/O Request
     |
     v
Device
     |
     v
Hardware/service 

Functions such as:
OpenDevice()
DoIO()
SendIO()

 provide access to this mechanism. 

25. Asynchronous I/O 

A particularly powerful technique is asynchronous I/O. 

Instead of:
Start operation
      |
      v
Wait doing nothing
      |
      v
Finished the application may:
Start operation
      |
      +------------------+
      |                  |
      v                  v
Device works       Program continues
      |                  |
      +---------+--------+
                |
             completion 

This fits naturally into AmigaOS multitasking. 

26. Waiting for Multiple Events 

An application may need to wait for several things simultaneously. 

For example:
GUI message
Serial input
Timer event
Other process 

Exec signals allow a task to wait for combinations of events. 

Conceptually:
                  Application
                       |
                     Wait()
                       |
        +--------------+--------------+
        |              |              |
     GUI event     Timer event     I/O event 

Whichever relevant event occurs can wake the task. This is an extremely powerful feature for event-driven software. 

27. Example: A Terminal Program 

Imagine a serial terminal application. It needs to react to: 
keyboard input,
window events,
incoming serial data.
A primitive design might constantly poll all three. 

But an AmigaOS-oriented design can use asynchronous communication.
                 Terminal Task
                      |
                    Wait()
                      |
        +-------------+-------------+
        |             |             |
    Intuition      Keyboard      Serial I/O
      event          event          event 

The program sleeps when nothing needs attention. When something happens, it wakes and processes the event. 

28. Why This Is Efficient 

Consider ten applications running simultaneously. If every application continuously executed loops like:
check
check
check
check
check 

they would waste significant processor time. 

If idle applications instead call:
Wait() 

Exec can schedule CPU time for applications that actually have work to perform. This was especially important on early Amigas running at approximately 7 MHz. Efficient event-driven design allowed the system to feel responsive despite limited processor power. 

29. Resource Ownership 

Amiga programming places significant responsibility on the programmer. 

If your application opens:
library
device
window
screen
file
message port
memory block it should normally release the resource appropriately. 

For example:
OpenLibrary()
     |
     v
CloseLibrary()
AllocMem()
     |
     v
FreeMem()
Open()
  |
  v
Close()
OpenWindow()
     |
     v
CloseWindow() 

Correct cleanup is essential. 

30. Cleanup Order Matters 

Suppose a window depends on Intuition. You should not normally close intuition.library and then attempt to close the window using Intuition. The dependent resource should be released first. 

Conceptually:
Open library
     |
Open window
     |
Use window
     |
Close window
     |
Close library 

Notice the pattern. Resources are commonly released in roughly the reverse order in which they were acquired. 

31. Error Handling and Cleanup 

Imagine this startup sequence:
Open DOS
   |
Open Intuition
   |
Allocate memory
   |
Open window 

What happens if the memory allocation fails? The program must still release resources that were successfully obtained earlier. 

Conceptually:
Open DOS        OK
Open Intuition  OK
AllocMem        FAIL
        |
        v
Close Intuition
Close DOS
Exit 

Resource management is one of the most important practical skills in Amiga programming. 32. No Strong Memory Protection Classic AmigaOS makes correct programming especially important because applications normally share a common address space. 

A bad pointer can potentially corrupt:
application data
another application
library structures
Exec structures 

This is very different from modern operating systems with strong per-process memory protection. Consequently, a programming error can crash the entire machine. 

33. The Power and Danger of Shared Memory The shared-address-space architecture has an advantage: Communication can be extremely efficient. 

A message can contain a pointer to data.
Task A
  |
  | pointer
  v
Message
  |
  v
Task B 

The data does not necessarily need to be copied between isolated process address spaces. But the disadvantage is obvious. Task B must use the pointer correctly. There is relatively little protection if it does not. The Amiga architecture therefore favors speed and flexibility over isolation. 

34. OS-Friendly Graphics Programming 

An application using Intuition and Graphics might follow a structure such as:
Application
     |
     +--> intuition.library
     |       |
     |       +--> windows
     |       +--> gadgets
     |       +--> events
     |
     +--> graphics.library
             |
             +--> drawing
             +--> bitmaps
             +--> text 

The application requests services instead of manipulating the chipset directly. This provides better cooperation with the operating system. 

35. Direct Hardware Graphics 

Games and demos sometimes take another route:
Program
   |
   +--> Custom registers
   |
   +--> Copper
   |
   +--> Blitter
   |
   +--> DMA 

This provides much greater control over timing and hardware behavior. But it also ties the program more closely to a specific Amiga chipset configuration. 

36. Why Games Often Took Over the Machine Many classic games were designed for systems with limited CPU and memory resources. Maximum predictable performance mattered. A game might therefore disable or avoid much of the normal operating-system environment and directly control: 
display
interrupts
Copper
Blitter
audio
input

This created an environment where the game effectively owned the machine. The advantage was control. The cost was reduced compatibility with multitasking and future hardware configurations. 

37. Why Applications Usually Should Not Do This 

Consider a word processor. It benefits from: 
filesystem compatibility
printers
fonts
Workbench integration
multitasking
clipboard-style interaction
higher-level GUI functions

Taking exclusive control of the machine would throw away many valuable operating-system services. Therefore productivity applications normally have strong reasons to remain OS-friendly. 

38. System-Friendly Software Survives Better 

Software written through documented operating-system APIs has a better chance of working across different Amiga configurations. For example:
Amiga 500
Amiga 2000
Amiga 3000
Amiga 4000 may differ significantly in: CPU
chipset
RAM
storage
graphics capabilities

Operating-system abstraction helps hide some of those differences. Directly manipulating assumptions about one particular machine can reduce portability. 

39. A Small Conceptual Amiga Program 

We can now imagine the structure of a simple GUI application.
START
  |
  v
Open required libraries
  |
  v
Allocate resources
  |
  v
Open window
  |
  v
+----------------------+
| Wait for signals     |
|                      |
| Receive messages     |
|                      |
| Process events       |
|                      |
| Perform program work |
+----------+-----------+
           |
     close requested
           |
           v
Close window
  |
  v
Free memory
  |
  v
Close libraries
  |
  v
END This simple structure contains many of the concepts from our entire series. 40. What Happens When the User Clicks? Suppose the user clicks a gadget. A simplified path might look like:
Mouse hardware
      |
      v
Input system
      |
      v
Intuition
      |
      v
IntuiMessage
      |
      v
Application's message port
      |
      v
Signal
      |
      v
Application wakes
      |
      v
GetMsg()
      |
      v
Process gadget event
      |
      v
ReplyMsg() A simple mouse click therefore connects hardware, the operating system and the application. 41. What Happens When the Program Loads a Picture? Suppose the application then loads an image. Conceptually:
Application
     |
     v
dos.library
     |
     v
Filesystem
     |
     v
Storage
     |
     v
File data
     |
     v
Memory If the picture must eventually become a chipset-accessible bitmap:
Image data
     |
 CPU processing
     |
     v
Chip RAM bitmap
     |
     v
Graphics system / DMA
     |
     v
Display 

This links DOS, memory management and graphics together. 

42. What Happens When Nothing Happens? 

Perhaps the most elegant case is when the user does nothing. A correctly designed application can simply:
Wait() 

Meanwhile:
Exec
 |
 +--> runs another program
 |
 +--> handles system tasks
 |
 +--> waits for hardware events 

The application does not consume CPU time unnecessarily. This is one reason AmigaOS multitasking could work effectively on comparatively slow processors. 

43. The Programmer's Mental Model 

A useful way to think about Amiga programming is:
Do not ask:

"How do I control every piece of hardware?"

Instead ask:

"Which AmigaOS component provides the service I need?" 

Examples:
Need memory?
      -> Exec

Need files?
      -> dos.library

Need a window?
      -> intuition.library

Need drawing?
      -> graphics.library

Need low-level I/O?
      -> Exec devices

Need communication?
      -> Messages and ports

Need to wait efficiently?
      -> Signals 

This mental model makes AmigaOS programming much easier to understand. 

44. The Complete Application Stack 

We can now construct a more comprehensive model:
                         User
                          |
                          v
                     Application
                          |
        +-----------------+-----------------+
        |                 |                 |
       DOS            Intuition          Exec
        |                 |            /   |   </span>
        |             Graphics      Memory | Messages
        |                 |                |
     Handler              |             Devices
        |                 |                |
        +-----------------+----------------+
                          |
                     Hardware
                          |
                +---------+---------+
                |         |         |
              CPU       Memory   Custom Chips 

This is simplified, but it shows how an Amiga program fits into the larger system. 

45. Why Amiga Programming Is Interesting 

Amiga programming exposes the relationship between hardware and software more clearly than many modern systems. A programmer can work at several levels. 

At a high level:
OpenWindow()
Open()
Read()
Write() 

At a lower level:
Tasks
Signals
Messages
Devices
Interrupts 

And, when necessary:
DMA
Copper
Blitter
Custom registers 

The programmer can decide how far down the machine's architecture to go. That flexibility is one of the reasons the Amiga became such an attractive platform for programmers, game developers and demo coders. 

Conclusion An Amiga application operates inside a rich environment provided by AmigaOS. 

At the foundation sits 
Exec, managing: tasks
memory
signals
messages
devices
interrupts
Higher-level libraries provide more specialized functionality:
dos.library
intuition.library
graphics.library 

Applications obtain resources, use them, wait for events and release them when they are finished. 

A typical program therefore follows a pattern such as:
Initialize
     |
     v
Acquire resources
     |
     v
Perform work
     |
     v
Wait for events
     |
     v
Respond to messages
     |
     v
Release resources
     |
     v
Exit 

Most importantly, concepts that may have seemed unrelated earlier in this series now begin to connect. Memory allocation determines where an application's data lives. Libraries provide operating-system services. Processes and tasks allow applications to run simultaneously. Signals let programs sleep efficiently. Message ports allow components to communicate. AmigaDOS provides access to files and processes. Intuition turns hardware input into GUI events. Devices and interrupts connect software with hardware. The result is an operating system where many small mechanisms work together. And that is the key to understanding Amiga programming: You are not programming just the CPU. You are programming within an entire architecture of cooperating software and hardware components. In Article 19, the next logical step is to leave the comfortable OS abstraction behind and look at programming the Amiga custom chips directly, especially the Copper, Blitter, DMA registers and Chip RAM.