The Application Kit is the starting point for all applications. Its classes establish an application as an identifiable entity--one that can cooperate and communicate with other applications (including the Browser). It lays a foundation for the other kits. Before designing and building your application, you should secure a breathing familiarity with this basic Kit.
There are four parts to the Application Kit:
The messaging mechanism is implemented by a set of collaborating classes: BMessage objects bundle information so that it can be posted to a thread within the same application or sent to a thread in another application. BLooper objects run message loops in threads, getting messages as they arrive and dispatching them to BHandler objects. The BHandler's job is to respond to the message.
The system employs the messaging mechanism to carry basic input to applications--from the keyboard and mouse, from the Browser, and from other external sources; system messages drive what most applications do. Every application will be on the receiving end of at least some of these messages and must have handlers ready to respond to them.
Applications can also use the mechanism to create threads with a messaging interface, arrange communication among the threads, or exchange information with and issue commands to other applications.
The BApplication object also runs the application's main message loop, where it receives messages that concern the application as a whole. Externally, this object represents the application to other applications; internally, it's the center where application-wide services and global information can be found. Because of it's pivotal role, it's assigned to a global variable, be_app, to make it easily accessible.
Other kits--the Interface Kit in particular--won't work until a BApplication object has been constructed.
The messaging framework and the fundamentals of setting up a Be application are described in the following sections of this introduction. The BApplication class is documented beginning on page 21. The other classes follow in alphabetical order.
At minimum, a messaging service must provide the means for:
BMessage objects are parcels of information that can be transferred between threads. The message source constructs a BMessage object, adds whatever information it wants to it, and then passes the parcel to a function that delivers it to a destination.
A BMessage can hold structured data of any type or amount. When you add data to a message, you assign it a name and a type code. If more than one item of data is added with the same name and type, the BMessage creates an array of data for that name. The name and an index into the array are used to retrieve the data from the message.
The object also contains a command constant that says what the message is about. It's stored as a public data member (called what). The constant may:
Not all messages have data entries, but all should have a command constant. Sometimes the constant is sufficient to convey the entire message.
Both the source and the destination of a message must agree upon its format --the command constant and the names and types of data entries. 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. The more that message protocols are shared, the easier it is for applications to cooperate with each other and take advantage of each other's special features.
The software kits define protocols for a number of messages. They're discussed in the Message Protocols appendix.
Typically, when an application creates an object, it retains responsibility for it; it's up to the application to free the objects it allocates when they're no longer needed. However, BMessage objects are an exception to this rule. Whenever a BMessage is passed to the messaging mechanism, ownership is passed with it. It's a little like mailing a letter --once you drop it at the post office, it no longer belongs to you.
The system takes responsibility for a delivered BMessage object and will eventually delete it--after the receiver is finished responding to it. A message receiver can assert responsibility for a message--essentially replacing the system as its owner--by detaching it from the messaging mechanism (with BLooper's DetachCurrentMessage() function).
In the Be model, messages are delivered to threads running message loops . Arriving messages are placed in a queue, and are then taken from the queue one at a time. After getting a message from the queue, the thread decides how it should be handled and dispatches it to an object that can respond. When the response is finished, the thread deletes the message and takes the next one from the queue--or, if the queue is empty, waits until another message arrives.
The message loop therefore dominates the thread. The thread does nothing but get messages and respond to them; it's driven by message input.
BLooper objects set up these message loops. A BLooper spawns a thread and sets the loop in motion. Posting a message to the BLooper delivers it to the thread (places it in the queue). The BLooper removes messages from the queue and dispatches them to BHandler objects. BHandlers are the objects ultimately responsible for received messages. Everything that the thread does begins with a BHandler's response to a message.
Two hook functions come into play in this process--one defined in the BLooper class and one declared by BHandler:
There's a close relationship between the BLooper role of running a message loop and the BHandler role of responding to messages. The BLooper class inherits from BHandler, so the same object can fill both roles. The BLooper is the default handler for the messages it receives.
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.
While a thread is responding to a message, it keeps the BLooper that dispatched the message locked. The thread locks the BLooper before calling DispatchMessage() and unlocks it after DispatchMessage() returns.
Special dispatching is provided for a subset of messages defined the system. These system messages are dispatched not by calling MessageReceived() , but by calling a BHandler hook function specific to the message.
System messages generally originate from within the Be operating system (from servers, the kits, or the Browser). They notify applications of external events, usually something the user has done--moved the mouse, pressed a key, resized a window, selected a document to open, or some other action of a similar sort. The command constant of the message names the event--for example, B_KEY_DOWN, B_SCREEN_CHANGED, or B_REFS_RECEIVED--and the message may carry data describing the event. The receiver is free to respond to the message (or to not respond) in any way that's appropriate.
A few system messages name an action the receiver is expected to take, such as B_ZOOM or B_ACTIVATE . The message tells the receiver what must be done. Even these messages are prompted by an event of some kind--such as the user clicking the zoom button in a window tab or picking an application to activate from the list of running applications.
System messages have a defined format. The command constant and the names and types of data entries are fixed for each kind of message. For example, the system message that reports a user keystroke on the keyboard--a "key-down" event--has B_KEY_DOWN as the command constant, a "when" entry for the time of the event, a "key" entry for the key that was hit, a "modifiers" entry for the modifier keys that were down at the time, and so on.
Although the set of system-defined messages is small, they're the most frequent messages for most applications. For example, when the user types a sentence, the application receives a series of B_KEY_DOWN messages, one for each keystroke.
System messages aren't delivered to just any BLooper object. The software kits derive a few specialized classes from BLooper to give significant entities in the application their own message loops. These are the objects that receive system messages and define how they're dispatched. Each message is matched to the specific kind of BLooper that's concerned with the particular event it reports or the particular instruction it delivers. Each type of message is delivered to a specific class of object.
In particular, both the BApplication class in this kit and the BWindow class in the Interface Kit derive from BLooper. The BApplication object runs a message loop in the main thread and receives messages that concern the application as a whole--such as requests to quit the application or to open a document. Each BWindow object runs in its own thread and receives messages that report activity in the user interface--including notifications that the user typed a particular character on the keyboard, moved the cursor on-screen, or pressed a mouse button. Every window that the user sees is represented by a separate BWindow object.
Each of these classes is concerned with only a subset of system messages --BApplication with application messages (discussed on page 16 below) and BWindow objects with interface messages (discussed in the chapter on the Interface Kit). In addition, the generic BLooper class defines how a small number of system management messages are dispatched; these messages have to do with the messaging system itself (and are discussed on page 15 of this chapter). Each class arranges for special handling of the system messages it's concerned with.
Every system message is dispatched by calling a specific virtual "hook" function, one that's matched to the message. For example, when the Application Server sends a B_KEY_DOWN message to the window where the user is typing, the BWindow determines which object is responsible for displaying typed characters and calls that object's KeyDown() virtual function. Similarly, a message that reports a user decision to shut down the application--a "quit-requested" event--is dispatched by calling the BApplication object's QuitRequested() function. Messages that report the movement of the cursor are dispatched by calling MouseMoved() , those that report a change in the screen configuration by calling ScreenChanged(), and so on.
These hook functions are declared in classes derived from BHandler and are often recognizable by their names. In the introductory chapter, it was explained that hook functions fall into three groups:
The hook functions that are matched to system messages can fall into any of these three categories. Since most system messages report events, they mostly fall into the first two categories. The function is named for the message, and the message for the event it reports.
However, if a system message delivers an instruction for the application to do something in particular, its hook function falls into the third group. The function is fully implemented in system software, but can be overridden by the application. The function is named for what it does, and the message is named for the function.
A BLooper picks a BHandler for a system message based on what the message is. For example, a BWindow calls upon the object that displays the current selection to handle a B_KEY_DOWN message. It asks the object in charge of the area where the user clicked to handle a B_MOUSE_DOWN message. And it handles messages that affect the window as a whole --such as, B_WINDOW_RESIZED --itself.
The BLooper identifies system messages by their command constants alone (their what data members). If a message is received and its command constant matches the constant for a system message, the BLooper will dispatch it by calling the message-specific hook function--regardless what data entries the message may have.
If the constant doesn't match a system message that the BLooper knows about, the message is dispatched by calling MessageReceived(). MessageReceived() is, therefore, reserved for application-defined messages. It's typically implemented to distribute the responsibility for received messages to other functions. That's something that's already taken care of for system messages, since each of them is mapped to its own function.
Although the system creates and delivers most messages, an application can create messages of its own and have them delivered to a chosen destination. There are three ways to initiate a message:
Messages are posted by calling a BLooper's PostMessage() function. PostMessage() inserts the message into the BLooper's queue so that it will be handled in sequence along with other messages the thread receives. Posting depends on the message source knowing the address of the destination BLooper; it therefore works only for application-internal messages.
Posting is how one thread of execution transfers control to another thread in the same application. Suppose, for example, that the main thread of an application (the BApplication object) receives a message requesting it to show something on-screen --begin displaying a video, say. It can create a window for this purpose, then post a message to the BWindow object telling it what to do. The BWindow receives the message and acts on it within the window's thread. After posting the message, the main thread is free to receive and respond to other messages while the window thread is busy with the video.
A thread might also post messages to itself, and thereby take advantage of the messaging mechanism to arrange its activity. This is what menu items and control devices do when they're invoked; they translate a message that reports a click or a keystroke into another, more specific message--one they could post anywhere, but typically deliver to the same thread.
Messages can be posted only within an application--where the thread that calls PostMessage() and the thread that responds to the message are in the same address space (are part of the same "team") and may even be the same thread.
To send a message to another address space, it's necessary to first set up a BMessenger object as a local representative of the remote destination. BMessengers can be constructed in two ways:
The first method constructs a BMessenger that can send messages to the main thread of the remote application, where they'll be received and handled by its BApplication object.
The second method constructs a BMessenger that's tied to a BHandler in your own application. However, you can place the BMessenger in a message and send it to a remote application. That application can then employ the BMessenger to target messages to your BHandler. The messages are delivered to whatever BLooper the BHandler belongs to; the BLooper dispatches the message to the BHandler.
Thus, a BMessenger can be seen as a local identifier for a remote BLooper/BHandler pair. Calling the object's SendMessage() function delivers the message to the remote destination.
(BMessengers can send messages to local destinations as well as to a remote ones. However, it's more efficient to post a local message than to send it.)
Through a service of the Interface Kit, users can drag messages from a source location and drop them on a chosen destination, perhaps in another application. The source application puts the message together and hands it over to the Application Server, which tracks where the user drags it.
When the user drops the message inside a window somewhere, the Server delivers it to the BWindow object and targets it to the BView (a kind of BHandler) that's in charge of the portion of the window where the message was dropped. The message is placed in the BWindow's queue and is dispatched like all other messages. In contrast to messages that are posted or sent in application code, only the user determines the destination of a dragged message.
A message receiver can discover whether and where a message was dropped by calling the BMessage object's WasDropped() and DropPoint() functions.
See Drag and Drop in The Interface Kit chapter for details on how to initiate a drag-and-drop session.
A delivered BMessage carries a return address with it < with the current exception of messages that are posted >. The message receiver can reply to the message by calling the BMessage's SendReply() function. Replies can be synchronous or asynchronous:
BMessage *reply; myMessenger->SendMessage(message, &reply); if (reply->what != B_NO_REPLY ) { . . . }
In this case, SendMessage() waits for the reply; it doesn't return until one is received. (In case the message receiver refuses to cooperate, a default reply is sent when the original message is deleted.)
A message receiver can discover whether the sender is waiting for a synchronous reply by calling the BMessage's IsSourceWaiting() function.
myMessenger->SendMessage(message, someHandler);
In this case, the sending function doesn't wait for the reply; the reply message will be directed to the named BHandler. An asynchronous reply is always possible. If a BHandler isn't designated for it, the BApplication object will act as the default handler.
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 *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.
You can also name a target BHandler for an asynchronous reply to a dragged message. < There is currently no provision for replying to a posted message. >
All messages have target BHandlers, whether explicitly or implicitly expressed.
The target is respected when the message is dispatched; the dispatcher always calls a hook function belonging to the designated BHandler. If the message matches one that the system defines and the target BHandler is the kind of object that's expected to handle that type of message, the dispatcher will call the target's message-specific hook function. However, if the designated target isn't the handler of design for the message, the BLooper will call its MessageReceived() function.
For example, if a B_KEY_DOWN message is posted to a BWindow object and a BView is named as the target, the BWindow will dispatch the message by calling the BView's KeyDown() function. However, if the BWindow itself is named as the target, it will dispatch the message by calling its own MessageReceived() function. BView objects are expected to handle keyboard messages; BWindows are not.
By implementing a PreferredHandler() function, a BLooper can name the BHandler it prefers to be the target of the messages it receives. You can follow this recommendation when posting a message < but currently not when sending a message >, or you can ignore it. The preferred handler typically changes from time to time. Choosing the preferred handler is therefore a way of determining the message target at run time. For example, a BWindow's preferred handler is the object in charge of the current selection; it changes as the user changes the selection.
Incoming messages can be filtered before they're dispatched to a BHandler. You can arrange to have a filtering function examine the message before the BHandler's hook function is called.
The filtering function is contained in a BMessageFilter object, which also holds the criteria for when the filter should apply. The function, called Filter(), is defined in classes derived from BMessageFilter.
If a BMessageFilter is attached to a BHandler, it filters only messages destined for that BHandler. It it's attached as a common filter to a BLooper object, it can filter any message that the BLooper dispatches, no matter what the handler. (In addition to the list of common filters, a BLooper can, like other BHandlers, maintain a list of filters specific to its role as a target handler.)
Although the Application Kit implements the messaging mechanism and defines all the system messages, it handles only a few of them itself. Each system message has a particular import and falls within the scope of a particular kind of BLooper object. Most are associated with BWindow objects in the Interface Kit. But there are two BLooper classes in the Application Kit; each handles its own subset of system messages:
The BLooper class takes care of just two system messages; both are concerned with running the messaging mechanism:
The BLooper object dispatches these messages by calling a hook function matched to the message. The following table lists the hook functions that are called to initiate a response to system management messages and the base classes where those functions are declared:
Message type | Virtual function | Class |
---|---|---|
B_QUIT_REQUESTED | QuitRequested() | BLooper |
B_HANDLERS_REQUESTED | HandlersRequested() | BHandler |
Although it defines how these messages are treated, nothing in the Be operating system produces the message itself. It's up to applications to create the messages and arrange for their delivery.
See System Management Messages in the Message Protocols appendix for information on the content of system management messages, particularly B_HANDLERS_REQUESTED .
The nine application messages are an assortment of various reports and requests. One message delivers an instruction:
All the other application messages report events. Two of them notify the application of a change in its status:
Two of the messages are requests that the application typically makes of itself:
Other application messages report information from remote sources:
The system is the source of one repeated message:
All application messages are received by the BApplication object in the main thread. The BApplication object dispatches them all to itself; it doesn't delegate them to any other handler. The following chart lists the hook functions that are called to initiate the application's response to system messages and the base class where each function is declared:
Message type | Virtual function | Class |
---|---|---|
B_ACTIVATE | Activate() | BApplication |
B_READY_TO_RUN | ReadyToRun() | BApplication |
B_APP_ACTIVATED | AppActivated() | BApplication |
B_QUIT_REQUESTED | QuitRequested() | BLooper |
B_ABOUT_REQUESTED | AboutRequested() | BApplication |
B_ARGV_RECEIVED | ArgvReceived() | BApplication |
B_REFS_RECEIVED | RefsReceived() | BApplication |
B_PANEL_CLOSED | FilePanelClosed() | BApplication |
B_PULSE | Pulse() | BApplication |
QuitRequested() is first declared in the BLooper class. BApplication reinterprets it --and reimplements it--to mean a request to quit the whole application, not just one of its threads.
Only four application messages--B_APP_ACTIVATED, B_ARGV_RECEIVED, B_REFS_RECEIVED, and B_PANEL_CLOSED--contain any data; the rest are empty. See Application Messages in the Message Protocols appendix for details on the content of these messages.
There are just a couple of things that an application must do if it's to take its place as a well-known and cooperative resident on the BeBox:
The BApplication object is essential; every application must have one to handle messages from other applications, particularly the Browser. However, it's not sufficient by itself. In addition, the application must provide:
The icons, signature, and behavioral information are all stored in the same resources file as the executable binary. By locating them in resources, they become available even when the application isn't running.
Although these bits of information don't strictly belong to the Application Kit, they're relevant to how parts of the Kit work and, possibly, to how you design your application. They're therefore discussed here.
Use the Icon World application to set up application resources, as described in The Be User's Guide, published separately.
Every application needs an icon to represent it (in a Browser window, for example). It should provide a large (32 pixel 32 pixel) version of the icon and a smaller (16 pixel 16 pixel) version. This can be done by creating the icons in Icon World or by importing icons created elsewhere. Either way, Icon World will construct highlighted versions of both the small and large icons and install them all in resources of type 'ICON' (for the large version) and 'MICN' (for the "mini-icon").
If an application opens documents or has other associated files, it should provide large and small icons for them as well.
An application-information resource (named "app info" and typed 'APPI') holds other information that needs to be available--especially to the Browser--whether or not the application is running. This resource advertises the application's signature and its launch behavior, and possibly other behavioral idiosyncrasies as well. You can create it in Icon World's App Info menu.
A signature is simply a long integer that identifies an application. No two applications should have the same signature.
To make sure that the signature for your application is unique, you should register it with--or obtain it from--Be's Developer Support services (devsupport@be.com or, in a pinch, 1 (415) 462-4103). We'll try to make sure that no one else adopts the same signature.
Use Icon World's App Info menu to install the signature in the resource.
There are three possible launch behaviors that you can choose for your application. Each possibility is represented by a constant:
B_MULTIPLE_LAUNCH | Several instances of the application can be running at once. It can be launched any number of times from the same executable file. |
This is the normal behavior for most utilities, such as the compiler, tar , or Heap Watch. It's also appropriate for an application that can deal with only one document at a time, and therefore must be launched anew each time it's asked to handle another file. | |
B_SINGLE_LAUNCH | Normally, only one instance of the application can be running. However, if the user copies the executable file for the application, it can be launched once from each copy. |
This is the normal behavior for most applications, including applications that can deal with more than one document at a time. | |
B_EXCLUSIVE_LAUNCH | When the application is running, no other instance of the same application can be launched from any source. |
This is appropriate for applications that require exclusive ownership of a system resource, like the telephone line. |
In other words, B_EXCLUSIVE_LAUNCH applications are restricted by signature--only one instance of an application with that particular signature can be running at any given time. B_SINGLE_LAUNCH applications are restricted by executable file--there can be only one instance of an application launched from that particular executable. B_MULTIPLE_LAUNCH applications are unrestricted.
These categories affect how the Browser launches applications and communicates with them. In the Browser, a user can launch an application by picking the application itself or by picking one of its documents. Double-clicking an application icon picks the application, and double-clicking a document icon picks the document. Dragging a document icon and dropping it on the application icon picks both.
Whenever the user picks a B_MULTIPLE_LAUNCH application or picks one of its documents, the Browser always launches it anew. It doesn't matter whether another instance of the application is already running or not.
However, when the user picks a B_SINGLE_LAUNCH application, the Browser launches it only if an application launched from the same executable file isn't already running. Otherwise, it activates the running application. Similarly, when the user picks a document for a B_SINGLE_LAUNCH application, the Browser matches the document to an executable file and launches it only if a running application hasn't been launched from the same file. If one has been launched from the file, the Browser merely activates it and sends it a message identifying the document.
B_EXCLUSIVE_LAUNCH is even more restrictive than B_SINGLE_LAUNCH. When the user picks a B_EXCLUSIVE_LAUNCH application, or the document for a B_EXCLUSIVE_LAUNCH application, the Browser launches it only if an application with the same signature isn't already running.
Most applications don't need the extreme restrictiveness of B_EXCLUSIVE_LAUNCH and should choose between B_SINGLE_LAUNCH and B_MULTIPLE_LAUNCH. The choice should be informed by whether the application can have more than one file open at a time, whether multiple instances of the same application would make sense to the user, whether windows belonging to one instance might be confused for windows belonging to another instance, and similar considerations.
The best place to choose a launch behavior for your application is in IconWorld's App Info menu. If a choice isn't made, IconWorld picks B_SINGLE_LAUNCH by default. If an application doesn't have an application information resource, it's treated as being B_MULTIPLE_LAUNCH by default.
Resources can also publicize two other behaviors, similarly designated by constants:
B_ARGV_ONLY | The application doesn't participate in the messaging system. Therefore, the only information it can receive are command-line arguments, argc and argv, passed to the main() function. |
B_ARGV_ONLY is assumed if the application doesn't have a BApplication object. | |
B_BACKGROUND_APP | The application doesn't have a user interface and therefore shouldn't appear in the Browser's application list. |
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.