541 lines
20 KiB
Markdown
541 lines
20 KiB
Markdown
# Mojo C++ System API
|
|
This document is a subset of the [Mojo documentation](/mojo/README.md).
|
|
|
|
[TOC]
|
|
|
|
## Overview
|
|
The Mojo C++ System API provides a convenient set of helper classes and
|
|
functions for working with Mojo primitives. Unlike the low-level
|
|
[C API](/mojo/public/c/system/README.md) (upon which this is built) this library
|
|
takes advantage of C++ language features and common STL and `//base` types to
|
|
provide a slightly more idiomatic interface to the Mojo system layer, making it
|
|
generally easier to use.
|
|
|
|
This document provides a brief guide to API usage with example code snippets.
|
|
For a detailed API references please consult the headers in
|
|
[//mojo/public/cpp/system](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/README.md).
|
|
|
|
Note that all API symbols referenced in this document are implicitly in the
|
|
top-level `mojo` namespace.
|
|
|
|
## Scoped, Typed Handles
|
|
|
|
All types of Mojo handles in the C API are simply opaque, integral `MojoHandle`
|
|
values. The C++ API has more strongly typed wrappers defined for different
|
|
handle types: `MessagePipeHandle`, `SharedBufferHandle`,
|
|
`DataPipeConsumerHandle`, `DataPipeProducerHandle`, `TrapHandle`, and
|
|
`InvitationHandle`.
|
|
|
|
Each of these also has a corresponding, move-only, scoped type for safer usage:
|
|
`ScopedMessagePipeHandle`, `ScopedSharedBufferHandle`, and so on. When a scoped
|
|
handle type is destroyed, its handle is automatically closed via `MojoClose`.
|
|
When working with raw handles you should **always** prefer to use one of the
|
|
scoped types for ownership.
|
|
|
|
Similar to `std::unique_ptr`, scoped handle types expose a `get()` method to get
|
|
at the underlying unscoped handle type as well as the `->` operator to
|
|
dereference the scoper and make calls directly on the underlying handle type.
|
|
|
|
## Message Pipes
|
|
|
|
There are two ways to create a new message pipe using the C++ API. You may
|
|
construct a `MessagePipe` object:
|
|
|
|
``` cpp
|
|
mojo::MessagePipe pipe;
|
|
|
|
// NOTE: Because pipes are bi-directional there is no implicit semantic
|
|
// difference between |handle0| or |handle1| here. They're just two ends of a
|
|
// pipe. The choice to treat one as a "client" and one as a "server" is entirely
|
|
// a the API user's decision.
|
|
mojo::ScopedMessagePipeHandle client = std::move(pipe.handle0);
|
|
mojo::ScopedMessagePipeHandle server = std::move(pipe.handle1);
|
|
```
|
|
|
|
or you may call `CreateMessagePipe`:
|
|
|
|
``` cpp
|
|
mojo::ScopedMessagePipeHandle client;
|
|
mojo::ScopedMessagePipeHandle server;
|
|
mojo::CreateMessagePipe(nullptr, &client, &server);
|
|
```
|
|
|
|
There are also some helper functions for constructing message objects and
|
|
reading/writing them on pipes using the library's more strongly-typed C++
|
|
handles:
|
|
|
|
``` cpp
|
|
mojo::ScopedMessageHandle message;
|
|
mojo::AllocMessage(6, nullptr, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE, &message);
|
|
|
|
void *buffer;
|
|
mojo::GetMessageBuffer(message.get(), &buffer);
|
|
|
|
const std::string kMessage = "hello";
|
|
std::copy(kMessage.begin(), kMessage.end(), static_cast<char*>(buffer));
|
|
|
|
mojo::WriteMessageNew(client.get(), std::move(message),
|
|
MOJO_WRITE_MESSAGE_FLAG_NONE);
|
|
|
|
// Some time later...
|
|
|
|
mojo::ScopedMessageHandle received_message;
|
|
uint32_t num_bytes;
|
|
mojo::ReadMessageNew(server.get(), &received_message, &num_bytes, nullptr,
|
|
nullptr, MOJO_READ_MESSAGE_FLAG_NONE);
|
|
```
|
|
|
|
See [message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/message_pipe.h)
|
|
for detailed C++ message pipe API documentation.
|
|
|
|
## Data Pipes
|
|
|
|
Similar to [Message Pipes](#Message-Pipes), the C++ library has some simple
|
|
helpers for more strongly-typed data pipe usage:
|
|
|
|
``` cpp
|
|
mojo::DataPipe pipe;
|
|
mojo::ScopedDataPipeProducerHandle producer = std::move(pipe.producer);
|
|
mojo::ScopedDataPipeConsumerHandle consumer = std::move(pipe.consumer);
|
|
|
|
// Or alternatively:
|
|
mojo::ScopedDataPipeProducerHandle producer;
|
|
mojo::ScopedDataPipeConsumerHandle consumer;
|
|
mojo::CreateDataPipe(null, &producer, &consumer);
|
|
```
|
|
|
|
C++ helpers which correspond directly to the
|
|
[Data Pipe C API](/mojo/public/c/system/README.md#Data-Pipes) for immediate and
|
|
two-phase I/O are provided as well. For example:
|
|
|
|
``` cpp
|
|
uint32_t num_bytes = 7;
|
|
producer.WriteData("hihihi", &num_bytes, MOJO_WRITE_DATA_FLAG_NONE);
|
|
|
|
// Some time later...
|
|
|
|
char buffer[64];
|
|
uint32_t num_bytes = 64;
|
|
consumer.ReadData(buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
|
|
```
|
|
|
|
See [data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/data_pipe.h)
|
|
for detailed C++ data pipe API documentation.
|
|
|
|
## Shared Buffers
|
|
|
|
A new shared buffers can be allocated like so:
|
|
|
|
``` cpp
|
|
mojo::ScopedSharedBufferHandle buffer =
|
|
mojo::SharedBufferHandle::Create(4096);
|
|
```
|
|
|
|
This new handle can be cloned arbitrarily many times by using the underlying
|
|
handle's `Clone` method:
|
|
|
|
``` cpp
|
|
mojo::ScopedSharedBufferHandle another_handle = buffer->Clone();
|
|
mojo::ScopedSharedBufferHandle read_only_handle =
|
|
buffer->Clone(mojo::SharedBufferHandle::AccessMode::READ_ONLY);
|
|
```
|
|
|
|
And finally the library also provides a scoper for mapping the shared buffer's
|
|
memory:
|
|
|
|
``` cpp
|
|
mojo::ScopedSharedBufferMapping mapping = buffer->Map(64);
|
|
static_cast<int*>(mapping.get()) = 42;
|
|
|
|
mojo::ScopedSharedBufferMapping another_mapping = buffer->MapAtOffset(64, 4);
|
|
static_cast<int*>(mapping.get()) = 43;
|
|
```
|
|
|
|
When `mapping` and `another_mapping` are destroyed, they automatically unmap
|
|
their respective memory regions.
|
|
|
|
See [buffer.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/buffer.h)
|
|
for detailed C++ shared buffer API documentation.
|
|
|
|
## Native Platform Handles (File Descriptors, Windows Handles, *etc.*)
|
|
|
|
The C++ library provides several helpers for wrapping system handle types.
|
|
These are specifically useful when working with a few `//base` types, namely
|
|
`base::PlatformFile`, `base::SharedMemoryHandle` (deprecated), and various
|
|
strongly-typed shared memory region types like
|
|
`base::ReadOnlySharedMemoryRegion`. See
|
|
[platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/platform_handle.h)
|
|
for detailed C++ platform handle API documentation.
|
|
|
|
## Signals & Traps
|
|
|
|
For an introduction to the concepts of handle signals and traps, check out
|
|
the C API's documentation on
|
|
[Signals & Traps](/mojo/public/c/system/README.md#Signals-Traps).
|
|
|
|
### Querying Signals
|
|
|
|
Any C++ handle type's last known signaling state can be queried by calling the
|
|
`QuerySignalsState` method on the handle:
|
|
|
|
``` cpp
|
|
mojo::MessagePipe message_pipe;
|
|
mojo::DataPipe data_pipe;
|
|
mojo::HandleSignalsState a = message_pipe.handle0->QuerySignalsState();
|
|
mojo::HandleSignalsState b = data_pipe.consumer->QuerySignalsState();
|
|
```
|
|
|
|
The `HandleSignalsState` is a thin wrapper interface around the C API's
|
|
`MojoHandleSignalsState` structure with convenient accessors for testing
|
|
the signal bitmasks. Whereas when using the C API you might write:
|
|
|
|
``` c
|
|
struct MojoHandleSignalsState state;
|
|
MojoQueryHandleSignalsState(handle0, &state);
|
|
if (state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
the C++ API equivalent would be:
|
|
|
|
``` cpp
|
|
if (message_pipe.handle0->QuerySignalsState().readable()) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### Watching Handles
|
|
|
|
The [`mojo::SimpleWatcher`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/simple_watcher.h)
|
|
class serves as a convenient helper for using the
|
|
[low-level traps API](/mojo/public/c/system/README.md#Signals-Traps)
|
|
to watch a handle for signaling state changes. A `SimpleWatcher` is bound to a
|
|
single sequence and always dispatches its notifications on a
|
|
`base::SequencedTaskRunner`.
|
|
|
|
`SimpleWatcher` has two possible modes of operation, selected at construction
|
|
time by the `mojo::SimpleWatcher::ArmingPolicy` enum:
|
|
|
|
* `MANUAL` mode requires the user to manually call `Arm` and/or `ArmOrNotify`
|
|
before any notifications will fire regarding the state of the watched handle.
|
|
Every time the notification callback is run, the `SimpleWatcher` must be
|
|
rearmed again before the next one can fire. See
|
|
[Arming a Trap](/mojo/public/c/system/README.md#Arming-a-Trap) and the
|
|
documentation in `SimpleWatcher`'s header.
|
|
|
|
* `AUTOMATIC` mode ensures that the `SimpleWatcher` always either is armed or
|
|
has a pending notification task queued for execution.
|
|
|
|
`AUTOMATIC` mode is more convenient but can result in redundant notification
|
|
tasks, especially if the provided callback does not make a strong effort to
|
|
return the watched handle to an uninteresting signaling state (by *e.g.*,
|
|
reading all its available messages when notified of readability.)
|
|
|
|
Example usage:
|
|
|
|
``` cpp
|
|
class PipeReader {
|
|
public:
|
|
PipeReader(mojo::ScopedMessagePipeHandle pipe)
|
|
: pipe_(std::move(pipe)),
|
|
watcher_(mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) {
|
|
// NOTE: base::Unretained is safe because the callback can never be run
|
|
// after SimpleWatcher destruction.
|
|
watcher_.Watch(pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE,
|
|
base::Bind(&PipeReader::OnReadable, base::Unretained(this)));
|
|
}
|
|
|
|
~PipeReader() {}
|
|
|
|
private:
|
|
void OnReadable(MojoResult result) {
|
|
while (result == MOJO_RESULT_OK) {
|
|
mojo::ScopedMessageHandle message;
|
|
uint32_t num_bytes;
|
|
result = mojo::ReadMessageNew(pipe_.get(), &message, &num_bytes, nullptr,
|
|
nullptr, MOJO_READ_MESSAGE_FLAG_NONE);
|
|
DCHECK_EQ(result, MOJO_RESULT_OK);
|
|
messages_.emplace_back(std::move(message));
|
|
}
|
|
}
|
|
|
|
mojo::ScopedMessagePipeHandle pipe_;
|
|
mojo::SimpleWatcher watcher_;
|
|
std::vector<mojo::ScopedMessageHandle> messages_;
|
|
};
|
|
|
|
mojo::MessagePipe pipe;
|
|
PipeReader reader(std::move(pipe.handle0));
|
|
|
|
// Written messages will asynchronously end up in |reader.messages_|.
|
|
WriteABunchOfStuff(pipe.handle1.get());
|
|
```
|
|
|
|
## Synchronous Waiting
|
|
|
|
The C++ System API defines some utilities to block a calling sequence while
|
|
waiting for one or more handles to change signaling state in an interesting way.
|
|
These threads combine usage of the
|
|
[low-level traps API](/mojo/public/c/system/README.md#Signals-Traps)
|
|
with common synchronization primitives (namely `base::WaitableEvent`.)
|
|
|
|
While these API features should be used sparingly, they are sometimes necessary.
|
|
|
|
See the documentation in
|
|
[wait.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait.h)
|
|
and [wait_set.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h)
|
|
for a more detailed API reference.
|
|
|
|
### Waiting On a Single Handle
|
|
|
|
The `mojo::Wait` function simply blocks the calling sequence until a given
|
|
signal mask is either partially satisfied or fully unsatisfiable on a given
|
|
handle.
|
|
|
|
``` cpp
|
|
mojo::MessagePipe pipe;
|
|
mojo::WriteMessageRaw(pipe.handle0.get(), "hey", 3, nullptr, nullptr,
|
|
MOJO_WRITE_MESSAGE_FLAG_NONE);
|
|
MojoResult result = mojo::Wait(pipe.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
|
|
DCHECK_EQ(result, MOJO_RESULT_OK);
|
|
|
|
// Guaranteed to succeed because we know |handle1| is readable now.
|
|
mojo::ScopedMessageHandle message;
|
|
uint32_t num_bytes;
|
|
mojo::ReadMessageNew(pipe.handle1.get(), &num_bytes, nullptr, nullptr,
|
|
MOJO_READ_MESSAGE_FLAG_NONE);
|
|
```
|
|
|
|
`mojo::Wait` is most typically useful in limited testing scenarios.
|
|
|
|
### Waiting On Multiple Handles
|
|
|
|
`mojo::WaitMany` provides a simple API to wait on multiple handles
|
|
simultaneously, returning when any handle's given signal mask is either
|
|
partially satisfied or fully unsatisfiable.
|
|
|
|
``` cpp
|
|
mojo::MessagePipe a, b;
|
|
GoDoSomethingWithPipes(std:move(a.handle1), std::move(b.handle1));
|
|
|
|
mojo::MessagePipeHandle handles[2] = {a.handle0.get(), b.handle0.get()};
|
|
MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE,
|
|
MOJO_HANDLE_SIGNAL_READABLE};
|
|
size_t ready_index;
|
|
MojoResult result = mojo::WaitMany(handles, signals, 2, &ready_index);
|
|
if (ready_index == 0) {
|
|
// a.handle0 was ready.
|
|
} else {
|
|
// b.handle0 was ready.
|
|
}
|
|
```
|
|
|
|
Similar to `mojo::Wait`, `mojo::WaitMany` is primarily useful in testing. When
|
|
waiting on multiple handles in production code, you should almost always instead
|
|
use a more efficient and more flexible `mojo::WaitSet` as described in the next
|
|
section.
|
|
|
|
### Waiting On Handles and Events Simultaneously
|
|
|
|
Typically when waiting on one or more handles to signal, the set of handles and
|
|
conditions being waited upon do not change much between consecutive blocking
|
|
waits. It's also often useful to be able to interrupt the blocking operation
|
|
as efficiently as possible.
|
|
|
|
[`mojo::WaitSet`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h)
|
|
is designed with these conditions in mind. A `WaitSet` maintains a persistent
|
|
set of (not-owned) Mojo handles and `base::WaitableEvent`s, which may be
|
|
explicitly added to or removed from the set at any time.
|
|
|
|
The `WaitSet` may be waited upon repeatedly, each time blocking the calling
|
|
sequence until either one of the handles attains an interesting signaling state
|
|
or one of the events is signaled. For example let's suppose we want to wait up
|
|
to 5 seconds for either one of two handles to become readable:
|
|
|
|
``` cpp
|
|
base::WaitableEvent timeout_event(
|
|
base::WaitableEvent::ResetPolicy::MANUAL,
|
|
base::WaitableEvent::InitialState::NOT_SIGNALED);
|
|
mojo::MessagePipe a, b;
|
|
|
|
GoDoStuffWithPipes(std::move(a.handle1), std::move(b.handle1));
|
|
|
|
mojo::WaitSet wait_set;
|
|
wait_set.AddHandle(a.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
|
|
wait_set.AddHandle(b.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
|
|
wait_set.AddEvent(&timeout_event);
|
|
|
|
// Ensure the Wait() lasts no more than 5 seconds.
|
|
bg_thread->task_runner()->PostDelayedTask(
|
|
FROM_HERE,
|
|
base::Bind([](base::WaitableEvent* e) { e->Signal(); }, &timeout_event);
|
|
base::TimeDelta::FromSeconds(5));
|
|
|
|
base::WaitableEvent* ready_event = nullptr;
|
|
size_t num_ready_handles = 1;
|
|
mojo::Handle ready_handle;
|
|
MojoResult ready_result;
|
|
wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result);
|
|
|
|
// The apex of thread-safety.
|
|
bg_thread->Stop();
|
|
|
|
if (ready_event) {
|
|
// The event signaled...
|
|
}
|
|
|
|
if (num_ready_handles > 0) {
|
|
// At least one of the handles signaled...
|
|
// NOTE: This and the above condition are not mutually exclusive. If handle
|
|
// signaling races with timeout, both things might be true.
|
|
}
|
|
```
|
|
|
|
## Invitations
|
|
Invitations are the means by which two processes can have Mojo IPC bootstrapped
|
|
between them. An invitation must be transmitted over some platform-specific IPC
|
|
primitive (*e.g.* a Windows named pipe or UNIX domain socket), and the public
|
|
[platform support library](/mojo/public/cpp/platform/README.md) provides some
|
|
lightweight, cross-platform abstractions for those primitives.
|
|
|
|
For any two processes looking to be connected, one must send an
|
|
`OutgoingInvitation` and the other must accept an `IncomingInvitation`. The
|
|
sender can attach named message pipe handles to the `OutgoingInvitation`, and
|
|
the receiver can extract them from its `IncomingInvitation`.
|
|
|
|
Basic usage might look something like this in the case where one process is
|
|
responsible for launching the other.
|
|
|
|
``` cpp
|
|
#include "base/command_line.h"
|
|
#include "base/process/launch.h"
|
|
#include "mojo/public/cpp/platform/platform_channel.h"
|
|
#include "mojo/public/cpp/system/invitation.h"
|
|
#include "mojo/public/cpp/system/message_pipe.h"
|
|
|
|
mojo::ScopedMessagePipeHandle LaunchAndConnectSomething() {
|
|
// Under the hood, this is essentially always an OS pipe (domain socket pair,
|
|
// Windows named pipe, Fuchsia channel, etc).
|
|
mojo::PlatformChannel channel;
|
|
|
|
mojo::OutgoingInvitation invitation;
|
|
|
|
// Attach a message pipe to be extracted by the receiver. The other end of the
|
|
// pipe is returned for us to use locally.
|
|
mojo::ScopedMessagePipeHandle pipe =
|
|
invitation->AttachMessagePipe("arbitrary pipe name");
|
|
|
|
base::LaunchOptions options;
|
|
base::CommandLine command_line("some_executable")
|
|
channel.PrepareToPassRemoteEndpoint(&options, &command_line);
|
|
base::Process child_process = base::LaunchProcess(command_line, options);
|
|
channel.RemoteProcessLaunchAttempted();
|
|
|
|
OutgoingInvitation::Send(std::move(invitation), child_process.Handle(),
|
|
channel.TakeLocalEndpoint());
|
|
return pipe;
|
|
}
|
|
```
|
|
|
|
The launched process can in turn accept an `IncomingInvitation`:
|
|
|
|
``` cpp
|
|
#include "base/command_line.h"
|
|
#include "base/threading/thread.h"
|
|
#include "mojo/core/embedder/embedder.h"
|
|
#include "mojo/core/embedder/scoped_ipc_support.h"
|
|
#include "mojo/public/cpp/platform/platform_channel.h"
|
|
#include "mojo/public/cpp/system/invitation.h"
|
|
#include "mojo/public/cpp/system/message_pipe.h"
|
|
|
|
int main(int argc, char** argv) {
|
|
// Basic Mojo initialization for a new process.
|
|
mojo::core::Init();
|
|
base::Thread ipc_thread("ipc!");
|
|
ipc_thread.StartWithOptions(
|
|
base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
|
|
mojo::core::ScopedIPCSupport ipc_support(
|
|
ipc_thread.task_runner(),
|
|
mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
|
|
|
|
// Accept an invitation.
|
|
mojo::IncomingInvitation invitation = mojo::IncomingInvitation::Accept(
|
|
mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine(
|
|
*base::CommandLine::ForCurrentProcess()));
|
|
mojo::ScopedMessagePipeHandle pipe =
|
|
invitation->ExtractMessagePipe("arbitrary pipe name");
|
|
|
|
// etc...
|
|
return GoListenForMessagesAndRunForever(std::move(pipe));
|
|
}
|
|
```
|
|
|
|
Now we have IPC initialized between the two processes.
|
|
|
|
Also keep in mind that bindings interfaces are just message pipes with some
|
|
semantic and syntactic sugar wrapping them, so you can use these primordial
|
|
message pipe handles as mojom interfaces. For example:
|
|
|
|
``` cpp
|
|
// Process A
|
|
mojo::OutgoingInvitation invitation;
|
|
auto pipe = invitation->AttachMessagePipe("x");
|
|
mojo::Binding<foo::mojom::Bar> binding(
|
|
&bar_impl,
|
|
foo::mojom::BarRequest(std::move(pipe)));
|
|
|
|
// Process B
|
|
auto invitation = mojo::IncomingInvitation::Accept(...);
|
|
auto pipe = invitation->ExtractMessagePipe("x");
|
|
foo::mojom::BarPtr bar(foo::mojom::BarPtrInfo(std::move(pipe), 0));
|
|
|
|
// Will asynchronously invoke bar_impl.DoSomething() in process A.
|
|
bar->DoSomething();
|
|
```
|
|
|
|
And just to be sure, the usage here could be reversed: the invitation sender
|
|
could just as well treat its pipe endpoint as a `BarPtr` while the receiver
|
|
treats theirs as a `BarRequest` to be bound.
|
|
|
|
### Process Networks
|
|
Accepting an invitation admits the accepting process into the sender's connected
|
|
network of processes. Once this is done, it's possible for the newly admitted
|
|
process to establish communication with any other process in that network via
|
|
normal message pipe passing.
|
|
|
|
This does not mean that the invited process can proactively locate and connect
|
|
to other processes without assistance; rather it means that Mojo handles created
|
|
by the process can safely be transferred to any other process in the network
|
|
over established message pipes, and similarly that Mojo handles created by any
|
|
other process in the network can be safely passed to the newly admitted process.
|
|
|
|
### Invitation Restrictions
|
|
A process may only belong to a single network at a time.
|
|
|
|
Additionally, once a process has joined a network, it cannot join another for
|
|
the remainder of its lifetime even if it has lost the connection to its original
|
|
network. This restriction will soon be lifted, but for now developers must be
|
|
mindful of it when authoring any long-running daemon process that will accept an
|
|
incoming invitation.
|
|
|
|
### Isolated Invitations
|
|
It is possible to have two independent networks of Mojo-connected processes; for
|
|
example, a long-running system daemon which uses Mojo to talk to child processes
|
|
of its own, as well as the Chrome browser process running with no common
|
|
ancestor, talking to its own child processes.
|
|
|
|
In this scenario it may be desirable to have a process in one network talk to a
|
|
process in the other network. Normal invitations cannot be used here since both
|
|
processes already belong to a network. In this case, an **isolated** invitation
|
|
can be used. These work just like regular invitations, except the sender must
|
|
call `OutgoingInvitation::SendIsolated` and the receiver must call
|
|
`IncomingInvitation::AcceptIsolated`.
|
|
|
|
Once a connection is established via isolated invitation, Mojo IPC can be used
|
|
normally, with the exception that transitive process connections are not
|
|
supported; that is, if process A sends a message pipe handle to process B via
|
|
an isolated connection, process B cannot reliably send that pipe handle onward
|
|
to another process in its own network. Isolated invitations therefore may only
|
|
be used to facilitate direct 1:1 communication between two processes.
|