Messaging

The Application Kit provides a message-passing system lets your application send messages to and receives messages from other applications (including the Be-defined servers and apps), and from other threads in your own application.

The primary messaging classes are:

BMessage

Represents a message.

BHandler

Defines hook functions that are called to handle in-coming messages.

BLooper

Runs a loop that receives in-coming messages and figures out which BHandler should handle them.

BMessenger

Represents a message's destination (a combination of BLooper and BHandler), whether it's local or remote. The object is most useful for sending messages to other applications—you don't need it for local calls.

The other messaging classes are:

BMessageQueue

Is a FIFO that holds a BLooper's in-coming messages.

BMessageFilter

Is a device that can examine and (potentially) reject or re-route in-coming messages.

BInvoker

Is a convenience class that lets you treat a message and its target (the BHandler that will handle the message) as a single object.

BMessageRunner

Lets you send the same message over and over, at regular intervals.

The rest of this chapter looks at…

and describes how the classes fit together in the messaging system with an emphasis on what you can do in your application to take part.


Features of the Fundamental Classes

Looked at collectively, the four fundamental messaging classes comprise a huge chunk of API. Fortunately, the essential part of this API is pretty small; that's what we're going to look at here.

The BMessage Class

In the BMessage class, there's one essential data member, and two essential functions:

  • The what data member is an arbitrary uint32 value that describes (symbolically) what the message is about. You can set and examine what directly—you don't have to use functions to get to it. The what value is called the object's command constant. The BeOS defines some number of command constants (such as B_QUIT_REQUESTED, and B_MOUSE_DOWN), but you'll also be creating constants of your own. Keep in mind that the constant's value is meaningless—it's just a code that identifies the "intent" of the message (and it's only meaningful if the receiver recognizes the constant).

  • The two essential functions are AddData() and FindData(). These functions add data to a message you're about to send, and retrieve it from a message you just received. A BMessage can hold any amount of data; each data item (or "field") is identified by name, type, and index. For example, you can ask a message for the third boolean value named "IsEnabled" that it contains. In general, you use type-specific functions such as Add/FindString() and Add/FindInt32() rather than Add/FindData(). The query we just posed would actually look like this:

    /* The args are: name, index, value (returned by
       reference) */
    bool returnValue;
    aMessage.FindBool("IsEnabled", 2, &returnValue);

In summary, a BMessage contains (1) a command constant and (2) a set of data fields. Every BMessage that's used in the messaging system must have a command constant, but not every object needs to have data fields. (Other parts of the BeOS use BMessages for their data only. The BClipboard object, for example, ignores a BMessage's command constant.)

Note
Note

When discussing system-generated BMessage objects, we refer to the object by its command constant. For example, "a B_MOUSE_DOWN" means "a BMessage that has B_MOUSE_DOWN as its command constant".

Notice that a BMessage doesn't know how to send itself. However, as we'll see later, it does know how to reply to its sender once it's in the hands of the recipient.

The BLooper Class

BLooper's role is to receive messages and figure out what to do with them. There are four parts to this job, embodied in these functions:

  • Every BLooper spawns a thread in which it runs a message loop. It's in this thread that the object receives messages. But you have to kick the BLooper to get it to run; you do this by calling the Run() function. When you're done with the obejct—when you no longer need it to receive messages—you call Quit()

  • Although you never invoke it directly, DispatchMessage() is the guts of the message loop. All messages that the looper receives show up in individual invocations of DispatchMessage(). The function decides where the message should go next, which is mostly a matter of deciding whether (1) the message should be handled by a system-defined hook function, or (2) passed to BHandler's MessageReceived() function (which we'll talk about in a moment). Three other important aspects of DispatchMessage() are…

    1. It runs in the BLooper's message thread (or message loop); this is a separate thread that the object spawns specifically to receive in-coming messages.

    2. Individual DispatchMessage() invocations are synchronous with regard to the loop. In other words, when a message shows up, DispatchMessage() is called and runs to completion before the next message can be processed. (Messages that show up while DispatchMessage() is busy aren't lost—they're queued up in a BMessageQueue object.)

    3. It's fully implemented by BLooper (and kit classes derived from BLooper). You should rarely need to override it in your application.

  • The PostMessage() function delivers a message to the BLooper. In other words, it invokes DispatchMessage() in the looper's message thread. You call PostMessage() directly in your code. For example, here we create a BMessage and post it to our BApplication object (BApplication inherits from BLooper):

    /* This form of the BMessage constructor sets the command constant. */
    BMessage *myMessage = new BMessage(YOUR_CONSTANT_HERE);
    be_app->PostMessage(myMessage)

The BApplication and BWindow classes inherit from BLooper.

The BHandler Class

BHandler objects are called upon to handle the messages that a BLooper receives. A BHandler depends on two essential function:

MessageReceived()

Is the function that a BLooper calls to dispatch an in-coming message to the BHandler (the BMessage is passed as the function's only argument). This is a hook function that a BHandler subclass implements to handle the different types of messages that it expects to receive. Most implementations examine the message's command constant and go from there. A typical outline looks like this:

void MyHandler::MessageReceived(BMessage* message)
{
   /* Examine the command constant. */
   switch ( message->what ) {

   case YOUR_CONSTANT_HERE:
      /* Call a function that handles this sort of message. */
      HandlerFunctionA();
      break;

   case ANOTHER_CONSTANT_HERE:
      /* ditto */
      HandlerFunctionB();
      break;

   default:
      /* Messages that your class doesn't recognize must
       * be passed on to the base class implementation. */
      baseClass::MessageReceived(message);
      break;
   }
}
BLooper::AddHandler()

Is BHandler's other essential function and is defined by BLooper. This function adds the (argument) BHandler object to the (invoked-upon) BLooper's list of candidate handlers (its handler chain). If a BHandler wants to handle messages that are received by a BLooper, it must first be added to the BLooper's handler chain.

BLooper inherits from BHandler, and automatically adds itself to its own handler chain. This means that a BLooper can handle the messages that it receives—this is the default behaviour for most messages. We'll examine this issue in depth later in this chapter.

The other classes that inherit from BHandler are BView and BShelf (both in the Interface Kit).

The BMessenger Class

A BMessenger's most important feature is that it can send a message to a remote application. To do this takes two steps, which point out the class' essential features:

  • You identify the application that you want to send a message to (the "target") in the BMessenger constructor. An application is identified by its app signature (a MIME string).

  • The SendMessage() function sends a message to the target.

BMessengers can also be used to target local looper/handler pairs.


From Looper to Handler

A BLooper pops a message from its message queue and, within its DispatchMessage() function, dispatches the message by invoking a BHandler function. But (1) which BHandler and (2) which function?

Finding a Handler

First, let's answer the "which BHandler" question. To determine which BHandler should handle an in-coming message, a BLooper follows these steps:

  1. Does the BMessage target a specific BHandler? Both the BLooper::PostMessage() and BMessenger::SendMessage() functions provide additional arguments that let you specify a target handler that you want to have handle the message (you can also set the target in the BMessenger constructor). If a BHandler is specified, the BMessage will show up in that object's MessageReceived() function (or it will invoke a message-mapped hook function, as explained below).

  2. Does the BLooper designate a preferred handler? Through its SetPreferredHandler() function, a BLooper can designate one of the objects in its handler chain as its preferred handler, and passes all messages to that object.

  3. The BLooper handles the BMessage itself. If there's no target handler or preferred handler designation, the BLooper handles the message itself—in other words, the message is passed to the BLooper's own MessageReceived() function (or message-mapped hook).

We should mention here that both the BApplication and the BWindow class fine-tune this decision process a bit. However, the meddling that they do only applies to system messages (described below). The messages that you define yourself (i.e. the command constants that your application defines) will always follow the message dispatching path described here.

Note
Note

If you look at the DispatchMessage() protocol, you'll notice that it has a BMessage and a BHandler as arguments. In other words, the "which handler" decision described here is actually made before DispatchMessage() is called. In general, this is an implementation detail that you shouldn't worry about. If you want to think that DispatchMessage() makes the decision—and we've done nothing to disabuse you of this notion—go ahead and think it.

Finding a Function

As described above, a BLooper passes a BMessage to a BHandler by invoking the latter's MessageReceived() function. This is true of all messages that you create yourself, but it isn't true of certain messages that the system defines and sends. These system-generated messages (or system messages)—particularly those that report user events such as B_MOUSE_DOWN or B_APP_ACTIVATED - invoke specific hook functions.

For example, when the user presses a key, a B_KEY_DOWN message is sent to the active BWindow object.. From within its DispatchMessage() function, BWindow invokes the MouseDown() function of the BView that currently holds keyboard focus. (When a BView is made the focus of keyboard events, its window promotes it to preferred handler.)

So the question of "which function" is fairly simple: If the BMessage is a system message that's mapped to a hook function, the hook function is invoked. If it's not mapped to a hook function, the BHandler's MessageReceived() function is invoked.

Each chapter contains a list of system messages and the hook functions that they're mapped to. Note that not all system messages are mapped to hook functions; some of them do show up in MessageReceived().

Inheritance and the Handler Chain

Let's look at MessageReceived() again. It was asserted, in a code snippet shown earlier, that a typical MessageReceived() implementation should include an invocation of the base class' version of the function:

void MyHandler::MessageReceived(BMessage* message)
{
   switch ( message->what ) {

   /* Command constants that you handle go here. */

   default:
      /* Messages that your class doesn't recognize must be passed
      * on to the base class implementation. */
      baseClass::MessageReceived(message);
      break;
   }
}

This isn't just a good idea—it's an essential part of the messaging system. Forwarding the message to the base class does two things: It lets messages (1) pass up the class hierarchy, and (2) pass along the handler chain (in that order).

Passing up the class hierarchy is mostly straight-forward—it's no different for the MessageReceived() function than it is for any other function. But what happens at the top of the hierarchy—at the BHandler class itself—adds a small wrinkle. BHandler's implementation of MessageReceived() looks for the next handler in the BLooper's handler chain and invokes that object's MessageReceived() function.


Sending a Message

There are two functions that send messages to distinct recipients:

BLooper::PostMessage()

Can be used if the target (the BLooper that the BLooper::PostMessage() function is invoked upon) lives in the same application as the message sender.

BMessenger::SendMessage()

Lets you send messages to remote applications. The BMessenger object acts as a proxy for the remote app. (SendMessage() can also be used to send a message to a local BLooper, for reasons that we'll discuss later.)

The PostMessage() Function

You can post a message if the recipient BLooper is in your application:

myLooper->PostMessage(new BMessage(DO_SOMETHING), targetHandler);

As shown here, you can specify the handler that you want to have handle a posted message. The only requirement is that the BHandler must belong to the BLooper.

If the handler argument is NULL, the message is handled by the looper's preferred handler

myLooper->PostMessage(new BMessage(DO_SOMETHING), NULL);

By using the default handler, you let the looper decide who should handle the message.

Note
Note

The creator of the BMessage retains ownership and is responsible for deleting it when it's no longer needed.

The SendMessage() Function

If you want to send a message to another application, you have to use BMessenger's SendMessage() function. First, you construct a BMessenger object that identifies the remote app by signature…

BMessenger messenger("application/x-some-app");

…and then you invoke SendMessage():

messenger.SendMessage(new BMessage(DO_SOMETHING));
Note
Note

The creator of the BMessage retains ownership and is responsible for deleting it when it's no longer needed.

Handling a Reply

Every BMessage that you send identifies the application from which it was sent. The recipient of the message can reply to the message whether you (the sender) expect a reply or not. By default, reply messages are handled by your BApplication object. If you want reply messages to be handled by some other BHandler, you specify the object as a final argument to the PostMessage() or SendMessage() call:

myLooper->PostMessage(new BMessage(DO_SOMETHING), targetHandler,
replyHandler);
/* and */
myMessenger.SendMessage(&message, replyHandler);

The reply is sent asynchronously with regard to the PostMessage() / SendMessage() function.

SendMessage() (only) lets you ask for a reply message that's handed back synchronously in the SendMessage() call itself:

BMessage reply;
myMessenger.SendMessage(&message, &reply);

SendMessage() doesn't return until a reply is received. A default message is created and returned if the recipient doesn't respond quickly enough.

Receiving a Message

BMessage's SendReply() function has the same syntax as SendMessage(), so it's possible to ask for a synchronous reply to a message that is itself a reply,

BMessage message(READY);
BMessage reply;
theMessage->SendReply(&message, &reply);
if ( reply->what != B_NO_REPLY ) {
   . . .
}

or to designate a BHandler for an asynchronous reply to the reply:

theMessage->SendReply(&message, someHandler);

In this way, two applications can maintain an ongoing exchange of messages.

Handler Associations

To be notified of an arriving message, a BHandler must "belong" to the BLooper; it must have been added to the BLooper's list of eligible handlers. The list can contain any number of objects, but at any given time a BHandler can belong to only one BLooper.

Handlers that belong to the same BLooper can be chained in a linked list. If an object can't respond to a message, the system passes the message to its next handler.

BLooper's AddHandler() function sets up the looper-handler association; BHandler's SetNextHandler() sets the handler-handler link.


Message Filters

The BMessageFilter class lets create filtering functions that examine and re-route (or reject) incoming messages before they're processed by a BLooper. Message filters can also be applied to individual BHandler objects.


Message Protocols

Both the source and the destination of a message must agree upon its format—the command constant and the names and types of data fields. They must also agree on details of the exchange—when the message can be sent, whether it requires a response, what the format of the reply should be, what it means if an expected data item is omitted, and so on.

None of this is a problem for messages that are used only within an application; the application developer can keep track of the details. However, protocols must be published for messages that communicate between applications. You're urged to publish the specifications for all messages your application is willing to accept from outside sources and for all those that it can package for delivery to other applications.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.