API - BMessenger uniquely passed "by value"?

For foreign function interfaces, I need to know whether a parameter is passed by value, or by pointer.

C++ evidently has its own concept of “by value” semantics, wherein it supports by value parameter semantics, but underneath may choose to pass by pointer anyway. In the Haiku API, I encounter this with structs/classes that are commonly constructed like an ordinary C struct, like BPoint … and, curiously, with BMessenger.

Is there a rationale here, that would help me predict when some other class object might be passed by value-pointer? Is it really just BMessenger that gets this curious treatment, so I don’t need to be looking for a pattern?

This is a question for the C++ standard writers and gcc implementers, and maybe the x86_64 ABI designers.

I am not aware of anything special in that area. Passing by value is passing by value. At the ABI level, maybe smaller structs are passed directly in registers, and larger ones are passed by a copy in memory (on the stack)?

1 Like

What I mean is,

BMessenger is special.

It isn’t special to C++, unless there’s something I’m missing here - if we had a function that declared a by-value BHandler parameter, C++ would happily pass it by-value-pointer just the same as with BMessenger. But the Be API doesn’t do that - no function is declared to have a by-value BHandler parameter.

Or at least, I think no such function exists. That’s the question. Is there any other such object out there, that the Be API commonly declares “by value” as a parameter? (And is “opaque” like BMessenger - I know about BRect etc.) Is there something about BMessenger that explains this choice?

I don’t think I’m stuck, if there aren’t answers to these inane questions, just seemed like an unexplained knob sticking out.

In terms of the underlying “real” mechanism, I’m just treating C as “real”, that evidently being the interface standard for practical purposes. I write a main program that passes a struct to an external function “by value”, and implement the function in C with mangled name. If the value comes through OK, fine. If it doesn’t, try the C function with a pointer parameter. Whatever works with the C function, is what’s really going on, for my purposes.

Some classes will have a private copy constructor, in that case, passing them by value is not possible.

In other cases, it’s just a decision of whoever wrote the API, possibly they just forgot to use a pointer and now we’re stuck with that design to keep the ABI stable. Unfortunately there doesn’t seem to be strict rules, not only about that, but also about object ownership (when you pass a pointer to a method, does ittake ownership of it, or is it your responsibbility to free it later).

Maybe people who worked on Rust or C# bindings recently could tell you what they found.

That’s just what it is, Rust. It has a bindgen application that does a fair job with the C++, except for this wrinkle - I have to correct struct pass-by-values to pass-by-pointer.

If there’s a serious effort to make official Rust bindings, it’s news to me. The there isn’t any serious problem, it works on a practical level, but I don’t think there’s any way to leverage the benefits of Rust out of a C++ interface like this. It’s a bunch of pointers to memory allocated by C++ new, and the Rust level has no idea what’s going on with it, so no lifetime/borrow/etc. logic, which is relaxing but not what we’re supposed to like Rust for. Everything is unsafe. Etc.

For the motivations behind it … I’ve seen some places in the Haiku source where someone went to some trouble to pass a struct “by value”, I guess the intent is something like mutable/constant - if you pass “by value”, maybe it’s really going to mechanically use a pointer, but anyway it’s effectively a const parameter. I can’t imagine what kind of C++ they were using back in the day, probably before Metrowerks, but anyway it could have worked the same, and someone wanted to keep BMessenger on the const side.

There are bindings here: GitHub - nielx/haiku-rs: Rust bindings for the Haiku C-API but it is far from complete. The basics of messaging should already be there I think?

I had not checked the code closely, it may be more of an interoperable rewrite of Haiku classes than a wrapper over the C++ ones (I’m not super familiar with Rust, but at a very quick glance it looks like that).

Yes, that’s the way to get to go to really benefit from Rust. It could be enough, too, for some applications. I have a network client, so there’s a separate thread running fully Rust client implementation, communicating with the UI with messages, so if I wanted to write the UI in C++, this ground-up message port implementation could pretty much obviate any need for C++ foreign functions.

I think it’s technique, that was popular in early 90s. To pass a struct by value instead bunch of params to achieve the same result.

?
I guess you could characterize BPoint as an alternative to a bunch of params, and in fact there’s often another overloaded function for x and y as parameters. But BMessenger is an opaque object. Or is that my problem, I just missed the story on BMessenger?

BMessenger is just 3 values: team_id team, port_id port, int32 handlerToken. This values are used to identify target BHandler, possibly in another process. BMessenger can be copied by value like BRect.

Can it be constructed like BRect, though? Normally in Rust, Ocaml or whatever, BRect will live in the calling language’s storage. From Rust’s perspective it’s passed by const reference, not value, because that’s what happens in the C perspective, but BRect and BPoint are different in that Rust can be making these objects itself.

BMessenger doesn’t seem to fit in this category. I guess I could use find_port() to get around the privacy of the port value in BLooper, don’t know about handlerToken, but it just looks like this is meant to be an opaque object.

Though the main problem with using Rust storage for objects in general, is that turning C++ destructors loose on it could be problematic, and I gather that isn’t problem here.

I think any struct can be passed “by value” in the C++ sense, just trying to get a handle on why BMessenger actually is.

As I said earler, someone in BeOS days did not find it useful to use a pointer as the struct is small enough. There is no reason in particular. In C++ you can do whatever you want, and in BeOS days there weren’t very strict rules about that (to some extent, there still isn’t). Do not expect a reasoning and logical explanation for everything.

I think the approach used by Nielx in the Haiku.rs crate makes sense: ignore the C++ object altogether, and write your own Rust code that does a similar thing. These low-level objects are not so complicated, and in the end, trying to reuse the C++ code will create so many lifetime problems, that end up costing more effort than just reimplementing the class in Rust.

The point, to my understanding at least, of a BMessenger is to be sent in a message to a remote application in such a way that it can be used to send a reply back to the sender. A BMessenger is a “return address” that is more useful than sending a pointer would be.

From the Messaging overview page of BeBook:

BMessenger represents a message’s destination (a combination of BLooper andBHandler), 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.”

A BMessage has an AddMessenger() method to facilitate this. This method takes a BMessenger object, not a pointer so it is passed by value. This is what the BeBook’s BMessage class details page has to say about it:

AddMessenger() takes a BMessenger object, whereas AddData() would be passed a pointer to an int32 and a pointer to a BMessenger. AddPointer() adds only the pointer it’s passed, not the data it points to. To accomplish the same thing, AddData() would take a pointer to the pointer. (The pointer will be valid only locally; it won’t be useful to a remote destination.)”

A BMessenger is passed by value using BMessage::AddMessenger() similar to AddRect() and AddPoint(). You could pass a pointer instead or a pointer to a pointer but that pointer wouldn’t be as useful to the remote application that receives the message.

The BMessenger object can then be used to send a reply back to the sender. The SendMessage() method in the BeBook’s BMessenger details page has the following to say:

“The target can respond to the message:

  • If you supply a reply BMessage, the response is synchronous, with an optional timeout (replyTimeout) that starts ticking after the original message has been delivered.
  • If you supply a reply target (replyMessenger or replyHandler), the response is asynchronous, and is sent to the reply target.
  • If you supply neither a reply message nor a reply target, the target’s response is sent to be_app_messenger.”

Hopefully this gives a better understanding of what BMessenger is about.

That totally makes sense, as far as he got. I will look at that, for BMessenger, but … for example, where I’m using it, it comes from a BWindow object – that is, the BMessenger constructor that takes a BHandler. I anticipate completion of the Rust interface kit somewhere in the 2040-2050 time frame if I work harder than usual at it, so … I won’t even be 100 years old … OK, I will get started! Ha ha, no. Like I said, it’s an idea if you want to couple a Rust implementation of something with a C++ UI, using messages, which is probably the most sensible architecture. But that isn’t what I’m doing.

I can pass Rust storage to the constructor, if I want to get away from that pointer to external storage business. That doesn’t work at all well for just every BWhatever object, but I think I’m hearing that BMessenger is “just” data, so maybe OK. (As with passing structs “by value”, C++ struct “function return” is really by parameter passed by pointer, so we get BMessenger_BMessenger(&mut BMessenger, ...))