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

Haiku BInvoker: Message Ownership, Targets, and Invocation Results

Use BInvoker without double-freeing its message, confusing a target with a reply handler, or treating successful asynchronous delivery as completed work.

BInvoker packages a command message and a target BMessenger into a reusable invocation object. Many controls use this pattern, but its base-class contract deserves attention on its own: it owns its default message, a target identifies where a command goes, and the result of Invoke() is not necessarily the result of the work requested.

Transfer the default message once

The BInvoker(BMessage*, BMessenger) constructor and SetMessage() take the supplied BMessage* directly; they do not copy it. The invoker deletes that message when replaced or destroyed. Pass a separately allocated message, and do not free it after transfer.

#include <Invoker.h>
#include <Message.h>
#include <Messenger.h>

enum { kRefreshRequested = 'rfrq' };

// target is a BMessenger already bound to the intended BHandler.
BInvoker refresh(new BMessage(kRefreshRequested), target);
status_t sent = refresh.Invoke();
if (sent != B_OK) {
    // Record or report the dispatch failure.
}

This is a focused fragment, not a complete application. The target handler must belong to a functioning looper and handle kRefreshRequested in its own message dispatch. A stack-allocated BMessage would be invalid here because the invoker would later delete it. Reusing the same pointer in two invokers would likewise create conflicting ownership.

SetMessage(nullptr) clears and deletes the old default message. If the invocation payload must include changing state, keep the default as a template, make a local copy, add current fields to the copy, and pass that copy to Invoke(&copy). The temporary is not transferred as the invoker’s stored default; the send operation uses its contents during the call. Avoid mutating the shared default from another thread while an invocation is being prepared.

Keep command target and reply handler separate

SetTarget() accepts a BMessenger or a handler/looper pair. A BMessenger is the routing identity; a raw handler pointer alone does not make a valid cross-thread delivery contract. The handler/looper overload can report B_BAD_VALUE when the handler has no looper and B_MISMATCHED_VALUES when the supplied handler does not belong to the supplied looper. Check the return value and verify that the intended target still exists at dispatch time.

Target() returning nullptr is not, by itself, proof of a broken invoker. The documented API also returns null for a remote target and for a looper’s preferred handler. Messenger() exposes the configured messenger, and IsTargetLocal() distinguishes an in-application target from one in another application. Use those observations instead of dereferencing Target() as a universal health check.

The reply handler is a different role. SetHandlerForReply() selects the handler for replies to messages sent by the target; with no explicit reply handler, documentation says replies go to the application object. HandlerForReply() still returns null when none was set. Design the request and reply protocol explicitly if the caller needs to learn whether the work succeeded. An Invoke() return of B_OK means the message was sent, not that the receiver completed the operation successfully.

Understand the notification path

Invoke() sends the supplied argument message, or the stored default when no argument is supplied. InvokeNotify() additionally participates in the invoker’s notification mechanism used by watching controls; it is not merely another spelling of Invoke(). Haiku’s API notes that a bare BInvoker::Invoke() does not itself call SendNotices(). A derived control that wants both a target command and observer notifications must implement that behavior deliberately.

This matters when a UI reacts twice or not at all. Trace which method the concrete control calls, whether it supplies a message or relies on a default, who receives the target command, and whether observers receive separate notices. Do not assume that receiving an observer notice proves that the target handled the command, or that a command was dispatched when only a notification was configured.

Test the lifecycle, not just the happy path

Exercise a valid local target, an absent default message, a target whose looper has shut down, and a reply that arrives after the initiating view begins teardown. Record dispatch errors without conflating them with application-level errors returned in a reply. If a control owns the invoker, establish whether its stored message is replaced during reconfiguration and ensure no other owner retains that pointer. These are ordinary message-lifecycle checks, but BInvoker makes them easy to miss because its call site is compact.

Related:

Sources:

Comments