Skip to content
Haiku OSDeep Dive Published Updated 7 min readViews unavailable

Haiku BApplication: Startup, Refs, and Orderly Shutdown

Structure Haiku BApplication startup and shutdown around InitCheck, Run, ReadyToRun, command-line and refs messages, looper ownership, and quit policy.

BApplication is the Application Kit’s process-level object. It connects a native application to Haiku’s registrar and message model, provides application callbacks, and inherits a looper for serialized event handling. Constructing one is more than creating a convenient global: it establishes the context in which roster access, startup messages, windows, and coordinated shutdown make sense.

Reliable applications treat startup as a lifecycle with explicit failure points. They validate the application object, preserve launch arguments and file references, create UI only when their model is ready, and stop other loopers before destroying shared state. A program that opens a window in a constructor, blocks the app thread during initialization, or quits while a worker still targets a window can behave correctly in a demo and fail during real launches or shutdown.

Construct once and check initialization

A typical native app constructs a BApplication with its registered signature before creating windows or using the global roster. The signature identifies the application to Haiku’s registrar and is also used by MIME associations and launch behavior. Keep it stable across versions and unique to the application. A typo or a development-only signature can make a built executable appear to be a different application.

The public API offers constructors that can report an initialization status. Check InitCheck() and handle failure before proceeding. A failed application-server or registrar connection is not an empty desktop; continuing to create windows can produce confusing partial behavior. Show a concise diagnostic where possible, log the status, and exit through one cleanup path.

Do not create multiple BApplication instances to simulate multiple documents. One application object represents the process-level event context. Use document windows, handlers, and model objects for multiple documents. If the program needs an auxiliary message loop, create a separate BLooper and give its lifetime an explicit relationship to the application.

Run() starts the message-driven phase

BApplication::Run() starts the application’s event loop and returns a thread_id. The application thread dispatches messages to handlers, including callbacks for application-level requests. Keep its work responsive: file parsing, network access, and large indexing operations should not run synchronously in a callback that must return quickly to process the next event.

ReadyToRun() is the normal hook for work that should occur after the application has entered its run phase. Use it to build initial windows or present a startup document after validating the model. Do not assume it runs before every incoming launch message or that a user-visible window must exist for the process to be healthy. A background utility can remain active without a main window if that is an intentional application design.

The callback should schedule long initialization rather than perform it inline. A progress window can be created first, then a worker can open data or contact a service. Send results back to the window’s looper using messages; do not update Interface Kit objects from a worker thread without the API’s required locking and thread rules.

Handle both command-line arguments and file references

ArgvReceived() and RefsReceived() represent different launch inputs. Command-line arguments arrive as an argument vector; file references arrive in a BMessage using the platform’s reference-receiving convention. An application may receive startup input when first launched or receive another request after the registrar identifies an already-running instance, depending on launch policy.

Keep the two handlers deliberately small. Parse arguments into validated model requests, check option bounds and paths, and route the actual work through the same document-opening or command path used by the GUI. In RefsReceived(), iterate the reference fields the application claims to support, check each lookup status, and report per-file errors. A missing field, stale entry_ref, or unsupported document should not crash the entire app or silently drop later references.

Do not convert every reference immediately to a path and persist that string as the only identity. Names can change and volumes can disappear. Resolve the reference when the operation runs, handle ordinary file-system errors, and let the user retry if a removable or network volume returns. File-panel selections, Tracker launches, and recent items should converge on one validated open pipeline.

AboutRequested() is a separate callback from document dispatch. It should present application information without doing expensive work or assuming a main window is still alive. If the app uses a custom about window, ensure repeated requests focus the existing instance or open one safe replacement rather than allocating an unlimited number.

QuitRequested() is a policy decision

QuitRequested() answers whether a quit request should proceed. It is the place to ask about unsaved documents or refuse while a critical operation is in a state that cannot be safely interrupted. It is not a replacement for cleanup in destructors, and returning true should not be interpreted as proof that every asynchronous worker has already stopped.

For multiple windows, decide how unsaved state is handled and keep the policy consistent across menu quit, application shutdown, and system logout. A window’s own QuitRequested() can protect its document, while the application’s callback can coordinate process-wide tasks. Avoid duplicate prompts for the same document. If save is asynchronous, keep the quit state pending and request quit again only when the save completes successfully.

Do not block the app looper indefinitely while asking a worker to stop. Signal cancellation, stop the worker from accepting new work, and use a bounded completion or teardown policy. Ensure a late worker result checks that its target still exists before attempting UI updates.

Other loopers and destruction order

BApplication exposes RegisterLooper() and UnregisterLooper() for loopers that should be quit before the application object is destroyed. Use this mechanism when a helper BLooper owns handlers or windows that depend on application state. Registration is not a substitute for understanding object ownership; it is a declared shutdown relationship.

Orderly shutdown generally means stop accepting new work, notify workers, wait or otherwise complete their bounded cleanup, close dependent windows and loopers, release model resources, then allow the application object to be destroyed. A worker holding a raw pointer to a handler must be stopped before the handler goes away. Prefer BMessenger for message delivery across loopers, but still handle delivery failure when the target has quit.

Avoid doing lengthy cleanup in MessageReceived() for the quit command. That holds the event loop open and can prevent the messages needed for shutdown from being processed. Separate request policy from resource teardown and record which component owns each asynchronous task.

A practical lifecycle skeleton

This sketch emphasizes sequencing rather than defining every message in a complete application:

int main()
{
    BApplication app("application/x-vnd.example-Editor");
    if (app.InitCheck() != B_OK)
        return 1;

    app.Run();
    return 0;
}

The subclass supplies ReadyToRun(), ArgvReceived(), RefsReceived(), and any quit policy it needs. It should preserve base-class behavior when overriding message dispatch and should not call Run() a second time. The minimal main() does not prove that launch arguments, references, windows, worker shutdown, or save prompts are correct; those behaviors require explicit tests.

Verification matrix

Launch the application with no arguments, with valid and invalid options, with one file reference, with several references, and with a deleted or offline reference. Exercise both first launch and delivery to an already-running instance. Confirm the correct callback runs, every accepted input reaches the same validation path, and the UI remains responsive during slow file operations.

Test InitCheck() failure handling, repeated About requests, quit with a clean document, quit with unsaved state, cancellation of a long task, and application exit while a worker result is in flight. Add an auxiliary looper and prove it is stopped before shared state is destroyed. Instrument launch signature, callback type, input count, and error status without logging sensitive document contents.

Acceptance means the app has one process-level application object, a clear readiness boundary, robust handling of arguments and refs, a nonblocking event loop, and a shutdown policy that respects every dependent looper and worker. BApplication then acts as the lifecycle coordinator Haiku intends, rather than a window factory with hidden ordering assumptions.

Related:

Sources:

Comments