1 # Mojo C++ System API 2 This document is a subset of the [Mojo documentation](/mojo/README.md). 3 4 [TOC] 5 6 ## Overview 7 The Mojo C++ System API provides a convenient set of helper classes and 8 functions for working with Mojo primitives. Unlike the low-level 9 [C API](/mojo/public/c/system/README.md) (upon which this is built) this library 10 takes advantage of C++ language features and common STL and `//base` types to 11 provide a slightly more idiomatic interface to the Mojo system layer, making it 12 generally easier to use. 13 14 This document provides a brief guide to API usage with example code snippets. 15 For a detailed API references please consult the headers in 16 [//mojo/public/cpp/system](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/README.md). 17 18 Note that all API symbols referenced in this document are implicitly in the 19 top-level `mojo` namespace. 20 21 ## Scoped, Typed Handles 22 23 All types of Mojo handles in the C API are simply opaque, integral `MojoHandle` 24 values. The C++ API has more strongly typed wrappers defined for different 25 handle types: `MessagePipeHandle`, `SharedBufferHandle`, 26 `DataPipeConsumerHandle`, `DataPipeProducerHandle`, `TrapHandle`, and 27 `InvitationHandle`. 28 29 Each of these also has a corresponding, move-only, scoped type for safer usage: 30 `ScopedMessagePipeHandle`, `ScopedSharedBufferHandle`, and so on. When a scoped 31 handle type is destroyed, its handle is automatically closed via `MojoClose`. 32 When working with raw handles you should **always** prefer to use one of the 33 scoped types for ownership. 34 35 Similar to `std::unique_ptr`, scoped handle types expose a `get()` method to get 36 at the underlying unscoped handle type as well as the `->` operator to 37 dereference the scoper and make calls directly on the underlying handle type. 38 39 ## Message Pipes 40 41 There are two ways to create a new message pipe using the C++ API. You may 42 construct a `MessagePipe` object: 43 44 ``` cpp 45 mojo::MessagePipe pipe; 46 47 // NOTE: Because pipes are bi-directional there is no implicit semantic 48 // difference between |handle0| or |handle1| here. They're just two ends of a 49 // pipe. The choice to treat one as a "client" and one as a "server" is entirely 50 // a the API user's decision. 51 mojo::ScopedMessagePipeHandle client = std::move(pipe.handle0); 52 mojo::ScopedMessagePipeHandle server = std::move(pipe.handle1); 53 ``` 54 55 or you may call `CreateMessagePipe`: 56 57 ``` cpp 58 mojo::ScopedMessagePipeHandle client; 59 mojo::ScopedMessagePipeHandle server; 60 mojo::CreateMessagePipe(nullptr, &client, &server); 61 ``` 62 63 There are also some helper functions for constructing message objects and 64 reading/writing them on pipes using the library's more strongly-typed C++ 65 handles: 66 67 ``` cpp 68 mojo::ScopedMessageHandle message; 69 mojo::AllocMessage(6, nullptr, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE, &message); 70 71 void *buffer; 72 mojo::GetMessageBuffer(message.get(), &buffer); 73 74 const std::string kMessage = "hello"; 75 std::copy(kMessage.begin(), kMessage.end(), static_cast<char*>(buffer)); 76 77 mojo::WriteMessageNew(client.get(), std::move(message), 78 MOJO_WRITE_MESSAGE_FLAG_NONE); 79 80 // Some time later... 81 82 mojo::ScopedMessageHandle received_message; 83 uint32_t num_bytes; 84 mojo::ReadMessageNew(server.get(), &received_message, &num_bytes, nullptr, 85 nullptr, MOJO_READ_MESSAGE_FLAG_NONE); 86 ``` 87 88 See [message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/message_pipe.h) 89 for detailed C++ message pipe API documentation. 90 91 ## Data Pipes 92 93 Similar to [Message Pipes](#Message-Pipes), the C++ library has some simple 94 helpers for more strongly-typed data pipe usage: 95 96 ``` cpp 97 mojo::DataPipe pipe; 98 mojo::ScopedDataPipeProducerHandle producer = std::move(pipe.producer); 99 mojo::ScopedDataPipeConsumerHandle consumer = std::move(pipe.consumer); 100 101 // Or alternatively: 102 mojo::ScopedDataPipeProducerHandle producer; 103 mojo::ScopedDataPipeConsumerHandle consumer; 104 mojo::CreateDataPipe(null, &producer, &consumer); 105 ``` 106 107 C++ helpers which correspond directly to the 108 [Data Pipe C API](/mojo/public/c/system/README.md#Data-Pipes) for immediate and 109 two-phase I/O are provided as well. For example: 110 111 ``` cpp 112 uint32_t num_bytes = 7; 113 producer.WriteData("hihihi", &num_bytes, MOJO_WRITE_DATA_FLAG_NONE); 114 115 // Some time later... 116 117 char buffer[64]; 118 uint32_t num_bytes = 64; 119 consumer.ReadData(buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE); 120 ``` 121 122 See [data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/data_pipe.h) 123 for detailed C++ data pipe API documentation. 124 125 ## Shared Buffers 126 127 A new shared buffers can be allocated like so: 128 129 ``` cpp 130 mojo::ScopedSharedBufferHandle buffer = 131 mojo::SharedBufferHandle::Create(4096); 132 ``` 133 134 This new handle can be cloned arbitrarily many times by using the underlying 135 handle's `Clone` method: 136 137 ``` cpp 138 mojo::ScopedSharedBufferHandle another_handle = buffer->Clone(); 139 mojo::ScopedSharedBufferHandle read_only_handle = 140 buffer->Clone(mojo::SharedBufferHandle::AccessMode::READ_ONLY); 141 ``` 142 143 And finally the library also provides a scoper for mapping the shared buffer's 144 memory: 145 146 ``` cpp 147 mojo::ScopedSharedBufferMapping mapping = buffer->Map(64); 148 static_cast<int*>(mapping.get()) = 42; 149 150 mojo::ScopedSharedBufferMapping another_mapping = buffer->MapAtOffset(64, 4); 151 static_cast<int*>(mapping.get()) = 43; 152 ``` 153 154 When `mapping` and `another_mapping` are destroyed, they automatically unmap 155 their respective memory regions. 156 157 See [buffer.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/buffer.h) 158 for detailed C++ shared buffer API documentation. 159 160 ## Native Platform Handles (File Descriptors, Windows Handles, *etc.*) 161 162 The C++ library provides several helpers for wrapping system handle types. 163 These are specifically useful when working with a few `//base` types, namely 164 `base::PlatformFile`, `base::SharedMemoryHandle` (deprecated), and various 165 strongly-typed shared memory region types like 166 `base::ReadOnlySharedMemoryRegion`. See 167 [platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/platform_handle.h) 168 for detailed C++ platform handle API documentation. 169 170 ## Signals & Traps 171 172 For an introduction to the concepts of handle signals and traps, check out 173 the C API's documentation on 174 [Signals & Traps](/mojo/public/c/system/README.md#Signals-Traps). 175 176 ### Querying Signals 177 178 Any C++ handle type's last known signaling state can be queried by calling the 179 `QuerySignalsState` method on the handle: 180 181 ``` cpp 182 mojo::MessagePipe message_pipe; 183 mojo::DataPipe data_pipe; 184 mojo::HandleSignalsState a = message_pipe.handle0->QuerySignalsState(); 185 mojo::HandleSignalsState b = data_pipe.consumer->QuerySignalsState(); 186 ``` 187 188 The `HandleSignalsState` is a thin wrapper interface around the C API's 189 `MojoHandleSignalsState` structure with convenient accessors for testing 190 the signal bitmasks. Whereas when using the C API you might write: 191 192 ``` c 193 struct MojoHandleSignalsState state; 194 MojoQueryHandleSignalsState(handle0, &state); 195 if (state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE) { 196 // ... 197 } 198 ``` 199 200 the C++ API equivalent would be: 201 202 ``` cpp 203 if (message_pipe.handle0->QuerySignalsState().readable()) { 204 // ... 205 } 206 ``` 207 208 ### Watching Handles 209 210 The [`mojo::SimpleWatcher`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/simple_watcher.h) 211 class serves as a convenient helper for using the 212 [low-level traps API](/mojo/public/c/system/README.md#Signals-Traps) 213 to watch a handle for signaling state changes. A `SimpleWatcher` is bound to a 214 single sequence and always dispatches its notifications on a 215 `base::SequencedTaskRunner`. 216 217 `SimpleWatcher` has two possible modes of operation, selected at construction 218 time by the `mojo::SimpleWatcher::ArmingPolicy` enum: 219 220 * `MANUAL` mode requires the user to manually call `Arm` and/or `ArmOrNotify` 221 before any notifications will fire regarding the state of the watched handle. 222 Every time the notification callback is run, the `SimpleWatcher` must be 223 rearmed again before the next one can fire. See 224 [Arming a Trap](/mojo/public/c/system/README.md#Arming-a-Trap) and the 225 documentation in `SimpleWatcher`'s header. 226 227 * `AUTOMATIC` mode ensures that the `SimpleWatcher` always either is armed or 228 has a pending notification task queued for execution. 229 230 `AUTOMATIC` mode is more convenient but can result in redundant notification 231 tasks, especially if the provided callback does not make a strong effort to 232 return the watched handle to an uninteresting signaling state (by *e.g.*, 233 reading all its available messages when notified of readability.) 234 235 Example usage: 236 237 ``` cpp 238 class PipeReader { 239 public: 240 PipeReader(mojo::ScopedMessagePipeHandle pipe) 241 : pipe_(std::move(pipe)), 242 watcher_(mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) { 243 // NOTE: base::Unretained is safe because the callback can never be run 244 // after SimpleWatcher destruction. 245 watcher_.Watch(pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE, 246 base::Bind(&PipeReader::OnReadable, base::Unretained(this))); 247 } 248 249 ~PipeReader() {} 250 251 private: 252 void OnReadable(MojoResult result) { 253 while (result == MOJO_RESULT_OK) { 254 mojo::ScopedMessageHandle message; 255 uint32_t num_bytes; 256 result = mojo::ReadMessageNew(pipe_.get(), &message, &num_bytes, nullptr, 257 nullptr, MOJO_READ_MESSAGE_FLAG_NONE); 258 DCHECK_EQ(result, MOJO_RESULT_OK); 259 messages_.emplace_back(std::move(message)); 260 } 261 } 262 263 mojo::ScopedMessagePipeHandle pipe_; 264 mojo::SimpleWatcher watcher_; 265 std::vector<mojo::ScopedMessageHandle> messages_; 266 }; 267 268 mojo::MessagePipe pipe; 269 PipeReader reader(std::move(pipe.handle0)); 270 271 // Written messages will asynchronously end up in |reader.messages_|. 272 WriteABunchOfStuff(pipe.handle1.get()); 273 ``` 274 275 ## Synchronous Waiting 276 277 The C++ System API defines some utilities to block a calling sequence while 278 waiting for one or more handles to change signaling state in an interesting way. 279 These threads combine usage of the 280 [low-level traps API](/mojo/public/c/system/README.md#Signals-Traps) 281 with common synchronization primitives (namely `base::WaitableEvent`.) 282 283 While these API features should be used sparingly, they are sometimes necessary. 284 285 See the documentation in 286 [wait.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait.h) 287 and [wait_set.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h) 288 for a more detailed API reference. 289 290 ### Waiting On a Single Handle 291 292 The `mojo::Wait` function simply blocks the calling sequence until a given 293 signal mask is either partially satisfied or fully unsatisfiable on a given 294 handle. 295 296 ``` cpp 297 mojo::MessagePipe pipe; 298 mojo::WriteMessageRaw(pipe.handle0.get(), "hey", 3, nullptr, nullptr, 299 MOJO_WRITE_MESSAGE_FLAG_NONE); 300 MojoResult result = mojo::Wait(pipe.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); 301 DCHECK_EQ(result, MOJO_RESULT_OK); 302 303 // Guaranteed to succeed because we know |handle1| is readable now. 304 mojo::ScopedMessageHandle message; 305 uint32_t num_bytes; 306 mojo::ReadMessageNew(pipe.handle1.get(), &num_bytes, nullptr, nullptr, 307 MOJO_READ_MESSAGE_FLAG_NONE); 308 ``` 309 310 `mojo::Wait` is most typically useful in limited testing scenarios. 311 312 ### Waiting On Multiple Handles 313 314 `mojo::WaitMany` provides a simple API to wait on multiple handles 315 simultaneously, returning when any handle's given signal mask is either 316 partially satisfied or fully unsatisfiable. 317 318 ``` cpp 319 mojo::MessagePipe a, b; 320 GoDoSomethingWithPipes(std:move(a.handle1), std::move(b.handle1)); 321 322 mojo::MessagePipeHandle handles[2] = {a.handle0.get(), b.handle0.get()}; 323 MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE, 324 MOJO_HANDLE_SIGNAL_READABLE}; 325 size_t ready_index; 326 MojoResult result = mojo::WaitMany(handles, signals, 2, &ready_index); 327 if (ready_index == 0) { 328 // a.handle0 was ready. 329 } else { 330 // b.handle0 was ready. 331 } 332 ``` 333 334 Similar to `mojo::Wait`, `mojo::WaitMany` is primarily useful in testing. When 335 waiting on multiple handles in production code, you should almost always instead 336 use a more efficient and more flexible `mojo::WaitSet` as described in the next 337 section. 338 339 ### Waiting On Handles and Events Simultaneously 340 341 Typically when waiting on one or more handles to signal, the set of handles and 342 conditions being waited upon do not change much between consecutive blocking 343 waits. It's also often useful to be able to interrupt the blocking operation 344 as efficiently as possible. 345 346 [`mojo::WaitSet`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h) 347 is designed with these conditions in mind. A `WaitSet` maintains a persistent 348 set of (not-owned) Mojo handles and `base::WaitableEvent`s, which may be 349 explicitly added to or removed from the set at any time. 350 351 The `WaitSet` may be waited upon repeatedly, each time blocking the calling 352 sequence until either one of the handles attains an interesting signaling state 353 or one of the events is signaled. For example let's suppose we want to wait up 354 to 5 seconds for either one of two handles to become readable: 355 356 ``` cpp 357 base::WaitableEvent timeout_event( 358 base::WaitableEvent::ResetPolicy::MANUAL, 359 base::WaitableEvent::InitialState::NOT_SIGNALED); 360 mojo::MessagePipe a, b; 361 362 GoDoStuffWithPipes(std::move(a.handle1), std::move(b.handle1)); 363 364 mojo::WaitSet wait_set; 365 wait_set.AddHandle(a.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); 366 wait_set.AddHandle(b.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); 367 wait_set.AddEvent(&timeout_event); 368 369 // Ensure the Wait() lasts no more than 5 seconds. 370 bg_thread->task_runner()->PostDelayedTask( 371 FROM_HERE, 372 base::Bind([](base::WaitableEvent* e) { e->Signal(); }, &timeout_event); 373 base::TimeDelta::FromSeconds(5)); 374 375 base::WaitableEvent* ready_event = nullptr; 376 size_t num_ready_handles = 1; 377 mojo::Handle ready_handle; 378 MojoResult ready_result; 379 wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result); 380 381 // The apex of thread-safety. 382 bg_thread->Stop(); 383 384 if (ready_event) { 385 // The event signaled... 386 } 387 388 if (num_ready_handles > 0) { 389 // At least one of the handles signaled... 390 // NOTE: This and the above condition are not mutually exclusive. If handle 391 // signaling races with timeout, both things might be true. 392 } 393 ``` 394 395 ## Invitations 396 Invitations are the means by which two processes can have Mojo IPC bootstrapped 397 between them. An invitation must be transmitted over some platform-specific IPC 398 primitive (*e.g.* a Windows named pipe or UNIX domain socket), and the public 399 [platform support library](/mojo/public/cpp/platform/README.md) provides some 400 lightweight, cross-platform abstractions for those primitives. 401 402 For any two processes looking to be connected, one must send an 403 `OutgoingInvitation` and the other must accept an `IncomingInvitation`. The 404 sender can attach named message pipe handles to the `OutgoingInvitation`, and 405 the receiver can extract them from its `IncomingInvitation`. 406 407 Basic usage might look something like this in the case where one process is 408 responsible for launching the other. 409 410 ``` cpp 411 #include "base/command_line.h" 412 #include "base/process/launch.h" 413 #include "mojo/public/cpp/platform/platform_channel.h" 414 #include "mojo/public/cpp/system/invitation.h" 415 #include "mojo/public/cpp/system/message_pipe.h" 416 417 mojo::ScopedMessagePipeHandle LaunchAndConnectSomething() { 418 // Under the hood, this is essentially always an OS pipe (domain socket pair, 419 // Windows named pipe, Fuchsia channel, etc). 420 mojo::PlatformChannel channel; 421 422 mojo::OutgoingInvitation invitation; 423 424 // Attach a message pipe to be extracted by the receiver. The other end of the 425 // pipe is returned for us to use locally. 426 mojo::ScopedMessagePipeHandle pipe = 427 invitation->AttachMessagePipe("arbitrary pipe name"); 428 429 base::LaunchOptions options; 430 base::CommandLine command_line("some_executable") 431 channel.PrepareToPassRemoteEndpoint(&options, &command_line); 432 base::Process child_process = base::LaunchProcess(command_line, options); 433 channel.RemoteProcessLaunchAttempted(); 434 435 OutgoingInvitation::Send(std::move(invitation), child_process.Handle(), 436 channel.TakeLocalEndpoint()); 437 return pipe; 438 } 439 ``` 440 441 The launched process can in turn accept an `IncomingInvitation`: 442 443 ``` cpp 444 #include "base/command_line.h" 445 #include "base/threading/thread.h" 446 #include "mojo/core/embedder/embedder.h" 447 #include "mojo/core/embedder/scoped_ipc_support.h" 448 #include "mojo/public/cpp/platform/platform_channel.h" 449 #include "mojo/public/cpp/system/invitation.h" 450 #include "mojo/public/cpp/system/message_pipe.h" 451 452 int main(int argc, char** argv) { 453 // Basic Mojo initialization for a new process. 454 mojo::core::Init(); 455 base::Thread ipc_thread("ipc!"); 456 ipc_thread.StartWithOptions( 457 base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); 458 mojo::core::ScopedIPCSupport ipc_support( 459 ipc_thread.task_runner(), 460 mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN); 461 462 // Accept an invitation. 463 mojo::IncomingInvitation invitation = mojo::IncomingInvitation::Accept( 464 mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine( 465 *base::CommandLine::ForCurrentProcess())); 466 mojo::ScopedMessagePipeHandle pipe = 467 invitation->ExtractMessagePipe("arbitrary pipe name"); 468 469 // etc... 470 return GoListenForMessagesAndRunForever(std::move(pipe)); 471 } 472 ``` 473 474 Now we have IPC initialized between the two processes. 475 476 Also keep in mind that bindings interfaces are just message pipes with some 477 semantic and syntactic sugar wrapping them, so you can use these primordial 478 message pipe handles as mojom interfaces. For example: 479 480 ``` cpp 481 // Process A 482 mojo::OutgoingInvitation invitation; 483 auto pipe = invitation->AttachMessagePipe("x"); 484 mojo::Binding<foo::mojom::Bar> binding( 485 &bar_impl, 486 foo::mojom::BarRequest(std::move(pipe))); 487 488 // Process B 489 auto invitation = mojo::IncomingInvitation::Accept(...); 490 auto pipe = invitation->ExtractMessagePipe("x"); 491 foo::mojom::BarPtr bar(foo::mojom::BarPtrInfo(std::move(pipe), 0)); 492 493 // Will asynchronously invoke bar_impl.DoSomething() in process A. 494 bar->DoSomething(); 495 ``` 496 497 And just to be sure, the usage here could be reversed: the invitation sender 498 could just as well treat its pipe endpoint as a `BarPtr` while the receiver 499 treats theirs as a `BarRequest` to be bound. 500 501 ### Process Networks 502 Accepting an invitation admits the accepting process into the sender's connected 503 network of processes. Once this is done, it's possible for the newly admitted 504 process to establish communication with any other process in that network via 505 normal message pipe passing. 506 507 This does not mean that the invited process can proactively locate and connect 508 to other processes without assistance; rather it means that Mojo handles created 509 by the process can safely be transferred to any other process in the network 510 over established message pipes, and similarly that Mojo handles created by any 511 other process in the network can be safely passed to the newly admitted process. 512 513 ### Invitation Restrictions 514 A process may only belong to a single network at a time. 515 516 Additionally, once a process has joined a network, it cannot join another for 517 the remainder of its lifetime even if it has lost the connection to its original 518 network. This restriction will soon be lifted, but for now developers must be 519 mindful of it when authoring any long-running daemon process that will accept an 520 incoming invitation. 521 522 ### Isolated Invitations 523 It is possible to have two independent networks of Mojo-connected processes; for 524 example, a long-running system daemon which uses Mojo to talk to child processes 525 of its own, as well as the Chrome browser process running with no common 526 ancestor, talking to its own child processes. 527 528 In this scenario it may be desirable to have a process in one network talk to a 529 process in the other network. Normal invitations cannot be used here since both 530 processes already belong to a network. In this case, an **isolated** invitation 531 can be used. These work just like regular invitations, except the sender must 532 call `OutgoingInvitation::SendIsolated` and the receiver must call 533 `IncomingInvitation::AcceptIsolated`. 534 535 Once a connection is established via isolated invitation, Mojo IPC can be used 536 normally, with the exception that transitive process connections are not 537 supported; that is, if process A sends a message pipe handle to process B via 538 an isolated connection, process B cannot reliably send that pipe handle onward 539 to another process in its own network. Isolated invitations therefore may only 540 be used to facilitate direct 1:1 communication between two processes. 541