Derived from: public BHandler
Declared in: <app/Looper.h>
A BLooper object runs a message loop in a thread that it spawns for that purpose. It offers applications a simple way of creating a thread with a message interface.
Various classes in the Be software kits derive from BLooper in order to associate threads with significant entities in the application and to set up message loops with special handling for system messages. In the Application Kit, the BApplication object runs a message loop in the application's main thread. (Unlike other BLoopers, the BApplication object doesn't spawn a separate thread, but takes over the thread in which the application was launched.) In the Interface Kit, each BWindow object runs a loop to handle messages that report activity in the user interface.
Constructing a BLooper object gets it ready to work, but doesn't actually begin the message loop. Its Run() function must be called to spawn the thread and initiate the loop. Some derived classes may choose to call Run() within the class constructor,
MyLooper::MyLooper(const char *name, long priority) : BLooper(name, priority) { . . . Run(); }
so that simply constructing the object yields a fully functioning message loop. Other classes may need to keep object initialization separate from loop initiation. (The BWindow class in the Interface Kit is an example of the former approach, BApplication of the latter.)
Messages are posted to the BLooper's thread by calling its PostMessage() function. This simply puts the message in a queue. Messages can also be delivered to the BLooper's queue--somewhat more indirectly--by a BMessenger object or by the SendReply() function of a BMessage object.
No matter how they get there, the thread takes messages from the queue one at a time, in the order that they arrive, and calls DispatchMessage() for each one. DispatchMessage() hands the message to a BHandler object; the BHandler kicks off the thread's specific response to the message.
Posting or sending a message to a thread initiates activity within that thread, beginning with the DispatchMessage() function. Since DispatchMessage() immediately transfers responsibility for incoming messages to BHandler objects, BHandlers determine what happens in the BLooper's thread. Everything that the thread does, it does through BHandlers responding to messages. The BLooper merely runs the posting and dispatching mechanism.
The BLooper object is locked when DispatchMessage() is called; it stays locked until the thread has finished responding to the message.
When a message is posted to a thread, a target BHandler can be named for it. Messages that aren't posted to a specific target are handled by the BLooper itself--in other words, the BLooper acts as the default handler. (The BLooper class derives from BHandler for just this reason.)
Thus, a BLooper object can play both roles--the BLooper role of running the message loop and the BHandler role of responding to messages. For it to act as a handler, you must derive a class from BLooper and define a MessageReceived() function that can respond to the messages dispatched to it.
However, the BLooper class can also be used without change, as it's defined in the Kit --as long as all messages are targeted to a another handler.
A BLooper keeps a list of the BHandler objects that are eligible for the messages it dispatches. AddHandler() places a BHandler in the list, and RemoveHandler() removes it. A BHandler can be associated with only one BLooper at a time. (The BLooper is an automatic member of the list; it cannot be removed and associated with another BLooper.)
A BHandler's Looper() function will reveal which BLooper it currently belongs to. The BLooper itself doesn't reveal the membership of its list.
A BHandler can't get messages dispatched by any BLooper except the one it's associated with. However, this eligibility constraint is imposed not by DispatchMessage(), but by the BMessenger constructor when a target BHandler is proposed for the messages it will send and by PostMessage() when a BHandler is named as the target of a message posted to the BLooper.
DispatchMessage() | Passes incoming messages to a BHandler; can be overridden to change the way certain messages or classes of messages are dispatched. |
QuitRequested() | Can be implemented to decide whether a request to terminate the message loop and destroy the BLooper should be honored or not. |
BLooper(const char *name = NULL, long priority = B_NORMAL_PRIORITY)
Assigns the BLooper object a name and sets up its message queue, but doesn't spawn a thread or begin the message loop. Call Run() to spawn the thread that the BLooper will oversee.Run() creates the thread at the specified priority level and initiates its message loop.
The priority determines how much attention the thread will receive from the scheduler, and consequently how much CPU time it will get relative to other threads. You must choose one of the discrete priority levels defined in kernel/OS.h; intermediate priorities are not possible. The defined priorities, from lowest to highest, are:
B_LOW_PRIORITY | For threads running in the background that shouldn't interrupt other threads. |
B_NORMAL_PRIORITY | For all ordinary threads, including the main thread. |
B_DISPLAY_PRIORITY | For threads associated with objects in the user interface, including window threads. |
B_URGENT_DISPLAY_PRIORITY | For interface threads that deserve more attention than ordinary windows. |
B_REAL_TIME_DISPLAY_PRIORITY | For threads that animate the on-screen display. |
B_URGENT_PRIORITY | For threads performing time-critical computations. |
B_REAL_TIME_PRIORITY | For threads that control real-time processes that need unfettered access to the CPUs. |
Some derived classes may want to call Run() in the constructor, so that the object is set in motion at the time it's created. This is what the BWindow class in the Interface Kit does. Other derived classes might want to keep a separation between constructing the object and running it. The BApplication class maintains this distinction.
BLooper objects should always be dynamically allocated (with new), never statically allocated on the stack.
See also: Run(), BHandler::SetName()
virtual ~BLooper(void)
Frees the message queue and all pending messages, stops the message loop, and destroys the thread in which it ran. BHandlers that have been added to the BLooper are not deleted.
With the exception of the BApplication object, BLoopers should be destroyed by calling the Quit() function (or QuitRequested()), not by using the delete operator.
See also: Quit()
virtual void AddHandler(BHandler *handler) virtual bool RemoveHandler(BHandler *handler)
AddHandler() adds handler to the BLooper's list of BHandler objects, and RemoveHandler() removes it. Only BHandlers that have been added to the list are eligible to respond to the messages the BLooper dispatches. (However, this constraint is imposed not by DispatchMessage() , but by PostMessage() and the BMessenger constructor.) A BHandler can belong to no more than one BLooper, but can change its affiliation from time to time.
AddHandler() also calls the handler's SetNextHandler() function to assign it the BLooper as its default next handler. RemoveHandler() calls the same function to set the handler's next handler to NULL.
AddHandler() fails if the handler already belongs to a BLooper. RemoveHandler() returns TRUE if it succeeds in removing the BHandler from the BLooper, and FALSE if not or if the handler doesn't belong to the BLooper in the first place. For either function to work, the BLooper must be locked.
See also: BHandler::Looper() , BHandler::SetNextHandler() , PostMessage(), the BMessenger class
BMessage *CurrentMessage(void) const BMessage *DetachCurrentMessage(void)
Both these functions return a pointer to the message that the BLooper's thread is currently processing, or NULL if it's currently between messages. That's all that CurrentMessage() does. DetachCurrentMessage() also detaches the message from the message loop, so that:
Since the message won't be deleted automatically, you have time to reply to it later. However, if the thread that initiated the message is waiting for a reply, you should send one (or get rid of the BMessage) without much delay. If a reply hasn't already been sent by the time the message is deleted, the BMessage destructor sends back a default B_NO_REPLY message to indicate that a real reply won't be forthcoming. But if the message isn't deleted and a reply isn't sent, the initiating thread will continue to wait. (BMessage's IsSourceWaiting() function will let you know whether the message source is waiting for a reply.)
Detaching a message is useful only when you want to stretch out the response to it beyond the end of the message cycle, perhaps passing responsibility for it to another thread while the BLooper's thread continues to get and respond to other messages.
Since the current message is passed as an argument to BLooper's DispatchMessage() and BHandler's MessageReceived() hook functions, you may never need to call CurrentMessage() to get hold of it.
However, classes derived from BLooper (BApplication and BWindow, in particular) dispatch system messages by calling a message-specific function, not MessageReceived(). Typically, these functions are passed only part of the information contained in the BMessage. In such a case, you will have to call CurrentMessage() to get complete information about the instruction or event the BMessage object reports.
For example, in the Interface Kit, a KeyDown() function might check whether the Control key was pressed at the time of the key-down event as follows:
void MyView::KeyDown(ulong key) { BMessage *message = Window()->CurrentMessage(); if ( message->FindLong("modifiers") & B_CONTROL_KEY ) { . . . } . . . }
See also: BHandler::MessageReceived(), BMessage::WasSent()
virtual void DispatchMessage(BMessage *message, BHandler *target)
Dispatches messages as they're received by the BLooper's thread. Precisely how they're dispatched depends on the message and the designated target BHandler. The BWindow and BApplication classes that derive from BLooper implement their own versions of this function to provide for special dispatching for system messages. Each class defines its own set of such messages.
The target may be the BHandler object that was named when the message was posted, the BHandler that was passed when the BMessenger was constructed, the handler that was designated as the target for a reply message, or (for a BWindow) the BView where the message was dropped. Or it might be the BLooper itself, acting in its capacity as the default handler. For system messages it may be NULL; if so, the dispatcher must figure out a target for the message based on the contents of the BMessage object.
DispatchMessage() is the first stop in the message-handling mechanism. The BLooper's thread calls it automatically as it reads messages from the queue--you never call it yourself.
BLooper's version of DispatchMessage() dispatches B_QUIT_REQUESTED messages that are targeted to the BLooper itself by calling its own QuitRequested() function. It dispatches B_HANDLERS_REQUESTED messages by calling the target's HandlersRequested() function. All other messages are forwarded to the target's MessageReceived() function.
You can override this function to dispatch the messages that your own application defines or recognizes. Of course, you can also just wait for these messages to fall through to MessageReceived() --the choice is yours. If you do override DispatchMessage() , you should:
For example:
void MyLooper::DispatchMessage(BMessage *msg, BHandler *target) { switch ( msg->what ) { case MY_MESSAGE1: . . . break; case MY_MESSAGE2: . . . break; default: inherited::DispatchMessage(msg, target); break; } }
Don't delete the messages you handle when you're through with them; they're deleted for you.
The system locks the BLooper before calling DispatchMessage() and keeps it locked for the duration of the thread's response to the message (until DispatchMessage() returns).
See also: the BMessage class, BHandler::MessageReceived(), QuitRequested()
virtual void HandlersRequested(BMessage *message)
Responds to a B_HANDLERS_REQUESTED message by sending a B_HANDLERS_INFO message in reply. The request is for BMessenger objects that can deliver messages targeted to BHandlers that have been added to the BLooper.
The incoming message may ask for a particular BHandler associated with the BLooper, or it may ask for all of them. If it has an entry named "index", the BLooper looks for the BHandler at that index in its list of eligible handlers. Otherwise, if the message has an entry labeled "name", the BLooper looks for the associated BHandler with that name. If it finds a BHandler object at the requested index or with the requested name, it places a BMessenger for that object in the B_HANDLERS_INFO reply under the name "handlers". However, if it can't find the requested object, it adds the B_BAD_INDEX or B_NAME_NOT_FOUND error constant to the reply message under the name "error".
If the incoming B_HANDLERS_REQUESTED message doesn't request a particular BHandler by index or name, the BLooper adds BMessengers for all eligible BHandlers to the "handlers" array of the reply. The array should contain at least one BMessenger, the one corresponding to the BLooper itself.
See also: BHandler::HandlersRequested()
bool Lock(void) void Unlock(void)
These functions provide a mechanism for locking data associated with the BLooper, so that one thread can't alter the data while another thread is in the middle of doing something that depends on it. Only one thread can have the BLooper locked at any given time. Lock() waits until it can lock the object, then returns TRUE. It returns FALSE only if the BLooper can't be locked at all --for example, if it was destroyed by another thread.
Calls to Lock() and Unlock() can be nested. If Lock() is called more than once from the same thread, it will take an equal number of Unlock() calls from that thread to unlock the BLooper. (If Lock() is called from another thread, it waits until the thread that owns the lock unlocks the BLooper. It then obtains the lock and returns TRUE .)
Locking is the basic mechanism for operating safely in a multithreaded environment. It's especially important for the kit classes derived from BLooper--BApplication and BWindow.
However, it's generally not necessary to lock a BLooper when calling functions defined in the class itself or in a derived class. For example, BApplication and BWindow functions are implemented to call Lock() and Unlock() when necessary. Moreover, the BLooper is locked for you whenever it dispatches a message. It remains locked until the response to the message is complete.
Functions you define in classes derived from BLooper (or from BApplication and BWindow) should also call Lock() and Unlock(). In addition, you should employ the locking mechanism when calling functions of a class that's closely associated with a BLooper--for example, when calling functions of a BView that's attached to a BWindow.
Although locking is important and useful, you shouldn't be too blithe about it. While you hold a BLooper's lock, no other thread can acquire it. If another thread calls a function that tries to lock, the thread will hang until you unlock. Each thread should hold the lock as briefly as possible.
See also: LockOwner()
inline thread_id LockOwner(void) const inline bool IsLocked(void) const
LockOwner() returns the thread that currently has the BLooper locked, or -1 if the BLooper isn't locked.
IsLocked() returns TRUE if the calling thread has the BLooper locked (if it's the lock owner) and FALSE if not (if some other thread is the owner or the BLooper isn't locked).
See also: Lock()
virtual BLooper *Looper(void) const
Overrides the BHandler version of this function to return the BLooper object itself. This prevents the BLooper from acting as a handler for messages posted to any other thread. A BLooper can take on the role of BHandler only for messages delivered to its own thread.
See also: BHandler::Looper() , PostMessage()
BMessageQueue *MessageQueue(void) const
Returns the queue that holds messages posted or sent to the BLooper's thread. You rarely need to examine the message queue directly; it's made available so you can cheat fate by looking ahead.
See also: the BMessageQueue class
long PostMessage(BMessage *message, BHandler *target = NULL) long PostMessage(ulong command, BHandler *target = NULL)
Places a message in the BLooper's message queue and arranges for it to be dispatched to the target BHandler. If a target isn't mentioned, the message will be dispatched to the BLooper. The BLooper acts as the default handler for all messages not specifically targeted to another object.
However, if the named target is associated with a different BLooper (if the target's Looper() function returns NULL or some other BLooper object), the posting fails and the message is deleted. (A BHandler must be associated with a particular BLooper before it can be the target for messages posted to that object. It can't get messages from any other BLooper except the one it belongs to. For example, BViews in the Interface Kit are restricted to receiving messages posted to the BWindows to which they're attached.)
Once posted, the BMessage object belongs to the BLooper's thread, so you should not modify it, post it again, assign it to some other object, or delete it. It will be deleted automatically after it has been received and responded to.
If a command is passed rather than a message, PostMessage() creates a BMessage object, initializes its what data member to command, and posts it. This simply saves you the step of constructing a BMessage when it won't contain any data. For example, this code
myWindow->PostMessage(command, target);
is equivalent to:
myWindow->PostMessage(new BMessage(command), target);
To post the message, the command version of this function calls the version that takes a full BMessage argument. Thus, if you override just the message version, you'll affect how both operate.
This function returns B_NO_ERROR if successful, B_MISMATCHED_VALUES if the posting fails because the proposed handler is invalid, and B_ERROR if it fails because the BLooper itself is invalid.
See also: BHandler::Looper() , DispatchMessage()
virtual BHandler *PreferredHandler(void) const
Implemented by derived classes to return a preferred BHandler for messages posted to the BLooper. This function simply informs those who are about to post messages to the BLooper who they might name as the message handler. For example:
myLooper->PostMessage(msg, myLooper->PreferredHandler());
The BLooper class itself doesn't do anything with the preferred handler; it's not a default value for any BLooper operation.
In the Interface Kit, BWindow objects name the current focus view as the preferred handler. This makes it possible for other objects--such as BMenuItems and BButtons --to target messages to the BView that's currently in focus, whatever view that may happen to be at the time. For example, by posting its messages to the window's preferred handler, a "Cut" menu item can make sure that it always acts on whatever view contains the current selection. See the chapter on the Interface Kit for information on windows, views, and the role of the focus view.
The BLooper version of this function simply returns NULL , to indicate that generic BLoopers don't have a preferred handler. Note, however, that when a NULL handler is passed to PostMessage() , that function designates the BLooper itself as the target. For example, if PreferredHandler() returned NULL in the line of code shown above, the message would be dispatched to myLooper by default. Thus, in effect, a generic BLooper is its own preferred handler, even though PreferredHandler() returns NULL.
See also: BControl::SetTarget() and BMenuItem::SetTarget() in the Interface Kit, PostMessage()
virtual void Quit(void)
Exits the message loop, frees the message queue, kills the thread, and deletes the BLooper object.
When called from the BLooper's thread, all this happens immediately. Any pending messages are ignored and destroyed. Because the thread dies, Quit() doesn't return.
However, when called from another thread, Quit() waits until all previously posted messages (all messages already in the queue) work their way through the message loop and are handled. It then destroys the BLooper and returns only after the loop, queue, thread, and object no longer exist.
Quit() therefore terminates the BLooper synchronously; when it returns, you know that everything has been destroyed. To quit the BLooper asynchronously, you can post a B_QUIT_REQUESTED message to the thread (that is, a BMessage with B_QUIT_REQUESTED as its what data member). PostMessage() places the message in the queue and returns immediately.
When it gets a B_QUIT_REQUESTED message, the BLooper calls the QuitRequested() virtual function. If QuitRequested() returns TRUE, as it does by default, it then calls Quit().
See also: QuitRequested()
virtual bool QuitRequested(void)
Implemented by derived classes to determine whether the BLooper should quit when requested to do so. The BLooper calls this function to respond to B_QUIT_REQUESTED messages. If it returns TRUE, the BLooper calls Quit() to exit the message loop, kill the thread, and delete itself. If it returns FALSE, the request is denied and no further action is taken.
BLooper's default implementation of QuitRequested() always returns TRUE.
A request to quit that's delivered to the BApplication object is, in fact, a request to quit the entire application, not just one thread. BApplication therefore overrides QuitRequested() to pass the request on to each window thread before shutting down.
For BWindow objects in the Interface Kit, a request to quit might come from the user clicking the window's close button (a quit-requested event for the window), from the user's decision to quit the application (a quit-requested event for the application), from a "Close" menu item, or from some other occurrence that forces the window to close.
Classes derived from BWindow typically implement QuitRequested() to give the user a chance to save documents before the window is destroyed, or to cancel the request.
If an application can be launched more than once (B_MULTIPLE_LAUNCH) and its entire interface is essentially contained in one window, quitting the window might be tantamount to quitting the application. In this case, the window's QuitRequested() function should pass the request along to the BApplication object. For example:
bool MyWindow::QuitRequested() { . . . be_app->PostMessage(B_QUIT_REQUESTED); return TRUE; }
After asking the application to quit, QuitRequested() returns TRUE to immediately dispose of the window. If it returns FALSE, BApplication's version of the function will again request the window to quit.
If you call QuitRequested() from your own code, be sure to also provide the code that calls Quit():
if ( myLooper->QuitRequested() ) myLooper->Quit();
See also: BApplication::QuitRequested() , Quit()
virtual thread_id Run(void)
Spawns a thread at the priority level that was specified when the BLooper was constructed and begins running a message loop in that thread. If successful, this function returns the thread identifier. If unsuccessful, it returns B_NO_MORE_THREADS or B_NO_MEMORY to indicate why.
A BLooper can be run only once. If called a second time, Run() returns B_ERROR, but doesn't disrupt the message loop already running. < Currently, it drops into the debugger so you can correct the error. >
The message loop is terminated when Quit() is called, or (potentially) when a B_QUIT_REQUESTED message is received. This also kills the thread and deletes the BLooper object.
See also: the BLooper constructor, the BApplication class, Quit()
virtual void SetCommonFilterList(BList *list) BList *CommonFilterList(void) const virtual void AddCommonFilter(BMessageFilter *filter) virtual void RemoveCommonFilter(BMessageFilter *filter)
These functions manage a list of filters that can apply to any message the BLooper receives, regardless of its target BHandler. They complement a similar set of functions defined in the BHandler class. When a filter is associated with a BHandler, it applies only to messages targeted to that BHandler. When it's associated with a BLooper as a common filter, it applies to all messages that the BLooper dispatches, regardless of the target.
In addition to the list of common filters, a BLooper can maintain a filter list in its role as a BHandler. As for other BHandlers, these filters apply only if the BLooper is the target of the message.
SetCommonFilterList() assigns the BLooper a new list of common filters, replacing any list previously assigned. The list must contain pointers to instances of the BMessageFilter class or, more usefully, instances of classes that derive from BMessageFilter. If list is NULL , the current list is removed without a replacement. CommonFilterList() returns the current list of common filters.
AddCommonFilter() adds a filter to the end of the list of common filters. It creates the BList object if it doesn't already exist. By default, BLoopers don't keep a BList of common filters until one is assigned or AddCommonFilter() is called for the first time. RemoveCommonFilter() removes a filter from the list. It returns TRUE if successful, and FALSE if it can't find the specified filter in the list (or the list doesn't exist). It leaves the BList in place even after removing the last filter.
For SetCommonFilterList(), AddCommonFilter() , and RemoveCommonFilter() to work, the BLooper must be locked.
See also: BHandler::SetFilterList(), Lock() , BMessageFilter
thread_id Thread(void) const team_id Team(void) const
These functions identify the thread that runs the message loop and the team to which it belongs. Thread() returns B_ERROR if Run() hasn't yet been called to spawn the thread and begin the loop. Team() should always return the application's team_id.
The Be Book, HTML Edition, for Developer Release 8 of the Be Operating System.
Copyright © 1996 Be, Inc. All rights reserved.
Be, the Be logo, BeBox, BeOS, BeWare, and GeekPort are trademarks of Be, Inc.
Last modified September 6, 1996.