Another feature of the Amiga OS is its system of message-based interprocess communication. Using this system, a task can send a message to a message port owned by another task. Tasks use this mechanism to do things like trigger events or share data with other tasks, including system tasks. Exec's message system is built on top of Exec's task signaling mechanism. Most Amiga applications programming (especially Intuition programming) relies heavily upon this message-based form of interprocess communication. When one task sends a message to another task's message port, the OS adds the message to the port's message queue. The message stays in this queue until the task that owns the port is ready to check its port for messages. Typically, a task has put itself to sleep while it is waiting for an event, like a message to arrive at its message port. When the message arrives, the task wakes up to look in its message port. The messages in the message port's queue are arranged in first-in-first-out (FIFO) order so that, when a task receives several messages, it will see the messages in the order they arrived at the port. A task can use a message to share any kind of data with another task. This is possible because a task does not actually transmit an entire message, it only passes a pointer to a message. When a task creates a message (which can have many Kilobytes of data attached to it) and sends it to another task, the actual message does not move. Essentially, when task A sends a message to task B, task A lends task B a chunk of its memory, the memory occupied by the message. After task A sends the message, it has relinquished that memory to task B, so it cannot touch the memory occupied by the message. Task B has control of that memory until task B returns the message to task A with the ReplyMsg() function. Let's look at an example. Many applications use Intuition windows as a source for user input. Without getting into too much detail about Intuition, when an application opens a window, it can set up the window so Intuition will send messages about certain user input events. Intuition sends these messages to a message port created by Intuition for use with this window. When an application successfully opens a window, it receives a pointer to a Window structure, which contains a pointer to this message port (Window.UserPort). For this example, we'll assume the window has been set up so Intuition will send a message only if the user clicks the window's close gadget. When Intuition opens the window in this example, it creates a message port for the task that opened the Window. Because the most common message passing system uses signals, creating this message port involves using one of the example task's 32 signals. The OS uses this signal to signal the task when it receives a message at this message port. This allows the task to sleep while waiting for a "Close Window" event to arrive. Since this simple example is only waiting for activity at one message port, it can use the WaitPort() function. WaitPort() accepts a pointer to a message port and puts a task to sleep until a message arrives at that port. This simple example needs two variables, one to hold the address of the window and the other to hold the address of a message. struct Message *mymsg; /*defined in <exec/ports.h> */ struct Window *mywin; /* defined in <intuition/intuition.h> */ ... /* at some point, this application would have successfully opened a */ /* window and stored a pointer to it in mywin. */ ... /* Here the application goes to sleep until the user clicks */ /* the window's close gadget. This window was set up so */ /* that the only time Intuition will send a message to this */ /* window's port is if the user clicks the window's close */ /* gadget. */ WaitPort(mywin->UserPort); while (mymsg = GetMsg(mywin->UserPort)) /* process message now or copy information from message */ ReplyMsg(mymsg); ... /* Close window, clean up */ The Exec function GetMsg() is used to extract messages from the message port. Since the memory for these messages belongs to Intuition, the example relinquishes each message as it finishes with them by calling ReplyMsg(). Notice that the example keeps on trying to get messages from the message port until mymsg is NULL. This is to make sure there are no messages left in the message port's message queue. It is possible for several messages to pile up in the message queue while the task is asleep, so the example has to make sure it replies to all of them. Note that the window should never be closed within this GetMsg() loop because the while statement is still accessing the window's UserPort. Note that each task with a Process structure (sometimes referred to as a process) has a special process message port, Process.pr_MsgPort. This message port is only for use by Workbench and the DOS library itself. No application should use this port for its own use! Waiting on Message Ports and Signals at the Same Time