aboutsummaryrefslogtreecommitdiff
path: root/mojo/edk/embedder/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'mojo/edk/embedder/README.md')
-rw-r--r--mojo/edk/embedder/README.md340
1 files changed, 327 insertions, 13 deletions
diff --git a/mojo/edk/embedder/README.md b/mojo/edk/embedder/README.md
index f976fcb..6def874 100644
--- a/mojo/edk/embedder/README.md
+++ b/mojo/edk/embedder/README.md
@@ -1,13 +1,327 @@
-Mojo Embedder API
-=================
-
-The Mojo Embedder API is an unstable, internal API to the Mojo system
-implementation. It should be used by code running on top of the system-level
-APIs to set up the Mojo environment (instead of directly instantiating things
-from src/mojo/edk/system).
-
-Example uses: Mojo shell, to set up the Mojo environment for Mojo apps; Chromium
-code, to set up the Mojo IPC system for use between processes. Note that most
-code should use the Mojo Public API (under src/mojo/public) instead. The
-Embedder API should only be used to initialize the environment, set up the
-initial MessagePipe between two processes, etc.
+# Mojo Embedder Development Kit (EDK)
+
+The Mojo EDK is a (binary-unstable) API which enables a process to use Mojo both
+internally and for IPC to other Mojo-embedding processes.
+
+Using any of the API surface in `//mojo/edk/embedder` requires (somewhat
+confusingly) a direct dependency on the GN `//mojo/edk/system` target. Despite
+this fact, you should never reference any of the headers in `mojo/edk/system`
+directly, as everything there is considered to be an internal detail of the EDK.
+
+## Basic Initialization
+
+In order to use Mojo in a given process, it's necessary to call
+`mojo::edk::Init` exactly once:
+
+```
+#include "mojo/edk/embedder/embedder.h"
+
+int main(int argc, char** argv) {
+ mojo::edk::Init();
+
+ // Now you can create message pipes, write messages, etc
+
+ return 0;
+}
+```
+
+As it happens though, Mojo is less useful without some kind of IPC support as
+well, and that's a second initialization step.
+
+## IPC Initialization
+
+You also need to provide the system with a background TaskRunner on which it can
+watch for inbound I/O from any of the various other processes you will later
+connect to it.
+
+Here we'll just create a new background thread for IPC and let Mojo use that.
+Note that in Chromium, we use the existing "IO thread" in the browser process
+and content child processes.
+
+```
+#include "base/threading/thread.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/scoped_ipc_support.h"
+
+int main(int argc, char** argv) {
+ mojo::edk::Init();
+
+ base::Thread ipc_thread("ipc!");
+ ipc_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO));
+
+ // As long as this object is alive, all EDK API surface relevant to IPC
+ // connections is usable and message pipes which span a process boundary will
+ // continue to function.
+ mojo::edk::ScopedIPCSupport ipc_support(
+ ipc_thread.task_runner(),
+ mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+
+ return 0;
+}
+```
+
+This process is now fully prepared to use Mojo IPC!
+
+Note that all existing process types in Chromium already perform this setup
+very early during startup.
+
+## Connecting Two Processes
+
+Now suppose you're running a process which has initialized Mojo IPC, and you
+want to launch another process which you know will also initialize Mojo IPC.
+You want to be able to connect Mojo interfaces between these two processes.
+Rejoice, because this section was written just for you.
+
+NOTE: For legacy reasons, some API terminology may refer to concepts of "parent"
+and "child" as a relationship between processes being connected by Mojo. This
+relationship is today completely orthogonal to any notion of process hierarchy
+in the OS, and so use of these APIs is not constrained by an adherence to any
+such hierarchy.
+
+Mojo requires you to bring your own OS pipe to the party, and it will do the
+rest. It also provides a convenient mechanism for creating such pipes, known as
+a `PlatformChannelPair`.
+
+You provide one end of this pipe to the EDK in the local process via
+`PendingProcessConnection` - which can also be used to create cross-process
+message pipes (see the next section) - and you're responsible for getting the
+other end into the remote process.
+
+```
+#include "base/process/process_handle.h"
+#include "base/threading/thread.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/pending_process_connection.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/embedder/scoped_ipc_support.h"
+
+// You write this. It launches a new process, passing the pipe handle
+// encapsulated by |channel| by any means possible (e.g. on Windows or POSIX
+// you may inhert the file descriptor/HANDLE at launch and pass a commandline
+// argument to indicate its numeric value). Returns the handle of the new
+// process.
+base::ProcessHandle LaunchCoolChildProcess(
+ mojo::edk::ScopedPlatformHandle channel);
+
+int main(int argc, char** argv) {
+ mojo::edk::Init();
+
+ base::Thread ipc_thread("ipc!");
+ ipc_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO));
+
+ mojo::edk::ScopedIPCSupport ipc_support(
+ ipc_thread.task_runner(),
+ mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+
+ // This is essentially always an OS pipe (domain socket pair, Windows named
+ // pipe, etc.)
+ mojo::edk::PlatformChannelPair channel;
+
+ // This is a scoper which encapsulates the intent to connect to another
+ // process. It exists because process connection is inherently asynchronous,
+ // things may go wrong, and the lifetime of any associated resources is bound
+ // by the lifetime of this object regardless of success or failure.
+ mojo::edk::PendingProcessConnection child;
+
+ base::ProcessHandle child_handle =
+ LaunchCoolChildProcess(channel.PassClientHandle());
+
+ // At this point it's safe for |child| to go out of scope and nothing will
+ // break.
+ child.Connect(child_handle, channel.PassServerHandle());
+
+ return 0;
+}
+```
+
+The launched process code uses `SetParentPipeHandle` to get connected, and might
+look something like:
+
+```
+#include "base/threading/thread.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/scoped_ipc_support.h"
+
+// You write this. It acquires the ScopedPlatformHandle that was passed by
+// whomever launched this process (i.e. LaunchCoolChildProcess above).
+mojo::edk::ScopedPlatformHandle GetChannelHandle();
+
+int main(int argc, char** argv) {
+ mojo::edk::Init();
+
+ base::Thread ipc_thread("ipc!");
+ ipc_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO));
+
+ mojo::edk::ScopedIPCSupport ipc_support(
+ ipc_thread.task_runner(),
+ mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+
+ mojo::edk::SetParentPipeHandle(GetChannelHandle());
+
+ return 0;
+}
+```
+
+Now you have IPC initialized between two processes. For some practical examples
+of how this is done, you can dig into the various multiprocess tests in the
+`mojo_system_unittests` test suite.
+
+## Bootstrapping Cross-Process Message Pipes
+
+Having internal Mojo IPC support initialized is pretty useless if you don't have
+any message pipes spanning the process boundary. Fortunately, this is made
+trivial by the EDK: `PendingProcessConnection` has a
+`CreateMessagePipe` method which synthesizes a new solitary message pipe
+endpoint for your immediate use, while also generating a magic token string that
+can be exchanged for the other end of the pipe via
+`mojo::edk::CreateChildMessagePipe`.
+
+The token exchange can be done by the same process (which is sometimes useful),
+or by the process that is eventually connected via `Connect()` on that
+`PendingProcessConnection`. This means that you can effectively pass message
+pipes on the commandline by passing a token string.
+
+We can modify our existing sample code as follows:
+
+```
+#include "base/command_line.h"
+#include "base/process/process_handle.h"
+#include "base/threading/thread.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/pending_process_connection.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/embedder/scoped_ipc_support.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "local/foo.mojom.h" // You provide this
+
+base::ProcessHandle LaunchCoolChildProcess(
+ const base::CommandLine& command_line,
+ mojo::edk::ScopedPlatformHandle channel);
+
+int main(int argc, char** argv) {
+ mojo::edk::Init();
+
+ base::Thread ipc_thread("ipc!");
+ ipc_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO));
+
+ mojo::edk::ScopedIPCSupport ipc_support(
+ ipc_thread.task_runner(),
+ mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+
+ mojo::edk::PlatformChannelPair channel;
+
+ mojo::edk::PendingProcessConnection child;
+
+ base::CommandLine command_line; // Assume this is appropriately initialized
+
+ // Create a new message pipe with one end being retrievable in the new
+ // process. Note that it doesn't matter whether we call CreateMessagePipe()
+ // before or after Connect(), and we can create as many different pipes as
+ // we like.
+ std::string pipe_token;
+ mojo::ScopedMessagePipeHandle my_pipe = child.CreateMessagePipe(&pipe_token);
+ command_line.AppendSwitchASCII("primordial-pipe", pipe_token);
+
+ base::ProcessHandle child_handle =
+ LaunchCoolChildProcess(command_line, channel.PassClientHandle());
+
+ child.Connect(child_handle, channel.PassServerHandle());
+
+ // We can start using our end of the pipe immediately. Here we assume the
+ // other end will eventually be bound to a local::mojom::Foo implementation,
+ // so we can start making calls on that interface.
+ //
+ // Note that this could even be done before the child process is launched and
+ // it would still work as expected.
+ local::mojom::FooPtr foo;
+ foo.Bind(local::mojom::FooPtrInfo(std::move(my_pipe), 0));
+ foo->DoSomeStuff(42);
+
+ return 0;
+}
+```
+
+and for the launched process:
+
+
+```
+#include "base/command_line.h"
+#include "base/run_loop/run_loop.h"
+#include "base/threading/thread.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/scoped_ipc_support.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "local/foo.mojom.h" // You provide this
+
+mojo::edk::ScopedPlatformHandle GetChannelHandle();
+
+class FooImpl : local::mojom::Foo {
+ public:
+ explicit FooImpl(local::mojom::FooRequest request)
+ : binding_(this, std::move(request)) {}
+ ~FooImpl() override {}
+
+ void DoSomeStuff(int32_t n) override {
+ // ...
+ }
+
+ private:
+ mojo::Binding<local::mojom::Foo> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(FooImpl);
+};
+
+int main(int argc, char** argv) {
+ base::CommandLine::Init(argc, argv);
+
+ mojo::edk::Init();
+
+ base::Thread ipc_thread("ipc!");
+ ipc_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO));
+
+ mojo::edk::ScopedIPCSupport ipc_support(
+ ipc_thread.task_runner(),
+ mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+
+ mojo::edk::SetParentPipeHandle(GetChannelHandle());
+
+ mojo::ScopedMessagePipeHandle my_pipe = mojo::edk::CreateChildMessagePipe(
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ "primordial-pipe"));
+
+ local::mojom::FooRequest foo_request;
+ foo_request.Bind(std::move(my_pipe));
+ FooImpl impl(std::move(foo_request));
+
+ // Run forever!
+ base::RunLoop().Run();
+
+ return 0;
+}
+```
+
+Note that the above samples assume an interface definition in
+`//local/test.mojom` which would look something like:
+
+```
+module local.mojom;
+
+interface Foo {
+ DoSomeStuff(int32 n);
+};
+```
+
+Once you've bootstrapped your process connection with a real mojom interface,
+you can avoid any further mucking around with EDK APIs or raw message pipe
+handles, as everything beyond this point - including the passing of other
+interface pipes - can be handled eloquently using public bindings APIs.
+
+See [additional Mojo documentation](
+ https://www.chromium.org/developers/design-documents/mojo) for more
+information.