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".
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…
The essential features of the four fundamental classes. ( "Features of the Fundamental Classes")
How a
BLooper
decides which
BHandler
should handle an in-coming message.
("From Looper to Handler")
The different methods for sending messages and receiving replies. ("Sending a Message").
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.
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.
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) */ boolreturnValue
;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 BMessage
s
for their data only. The
BClipboard
object, for example,
ignores a BMessage
's 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.
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…
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.
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.)
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 theBMessage
constructor sets the command constant. */BMessage
*myMessage
= newBMessage
(YOUR_CONSTANT_HERE
);be_app
->PostMessage
(myMessage
)
The
BApplication
and BWindow
classes inherit from
BLooper
.
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:
voidMyHandler
::MessageReceived
(BMessage*message
) { /* Examine the command constant. */ switch (message
->what
) { caseYOUR_CONSTANT_HERE
: /* Call a function that handles this sort of message. */HandlerFunctionA()
; break; caseANOTHER_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).
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.
BMessenger
s can also be used
to target local looper/handler pairs.
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?
First, let's answer the "which BHandler
"
question. To determine which
BHandler
should handle an in-coming message, a
BLooper
follows these
steps:
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).
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.
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.
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.
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()
.
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:
voidMyHandler
::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.
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.)
You can post a message if the recipient
BLooper
is in your application:
myLooper
->PostMessage
(newBMessage
(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
(newBMessage
(DO_SOMETHING
),NULL
);
By using the default handler, you let the looper decide who should handle the message.
The creator of the
BMessage
retains ownership and is responsible for deleting it when it's no
longer needed.
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
(newBMessage
(DO_SOMETHING
));
The creator of the
BMessage
retains ownership and is responsible for deleting it when it's no longer
needed.
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
(newBMessage
(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.
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.
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.
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.
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.