README.md
1# Mojo Embedder Development Kit (EDK)
2
3The Mojo EDK is a (binary-unstable) API which enables a process to use Mojo both
4internally and for IPC to other Mojo-embedding processes.
5
6Using any of the API surface in `//mojo/edk/embedder` requires (somewhat
7confusingly) a direct dependency on the GN `//mojo/edk/system` target. Despite
8this fact, you should never reference any of the headers in `mojo/edk/system`
9directly, as everything there is considered to be an internal detail of the EDK.
10
11## Basic Initialization
12
13In order to use Mojo in a given process, it's necessary to call
14`mojo::edk::Init` exactly once:
15
16```
17#include "mojo/edk/embedder/embedder.h"
18
19int main(int argc, char** argv) {
20 mojo::edk::Init();
21
22 // Now you can create message pipes, write messages, etc
23
24 return 0;
25}
26```
27
28As it happens though, Mojo is less useful without some kind of IPC support as
29well, and that's a second initialization step.
30
31## IPC Initialization
32
33You also need to provide the system with a background TaskRunner on which it can
34watch for inbound I/O from any of the various other processes you will later
35connect to it.
36
37Here we'll just create a new background thread for IPC and let Mojo use that.
38Note that in Chromium, we use the existing "IO thread" in the browser process
39and content child processes.
40
41```
42#include "base/threading/thread.h"
43#include "mojo/edk/embedder/embedder.h"
44#include "mojo/edk/embedder/scoped_ipc_support.h"
45
46int main(int argc, char** argv) {
47 mojo::edk::Init();
48
49 base::Thread ipc_thread("ipc!");
50 ipc_thread.StartWithOptions(
51 base::Thread::Options(base::MessageLoop::TYPE_IO));
52
53 // As long as this object is alive, all EDK API surface relevant to IPC
54 // connections is usable and message pipes which span a process boundary will
55 // continue to function.
56 mojo::edk::ScopedIPCSupport ipc_support(
57 ipc_thread.task_runner(),
58 mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN);
59
60 return 0;
61}
62```
63
64This process is now fully prepared to use Mojo IPC!
65
66Note that all existing process types in Chromium already perform this setup
67very early during startup.
68
69## Connecting Two Processes
70
71Now suppose you're running a process which has initialized Mojo IPC, and you
72want to launch another process which you know will also initialize Mojo IPC.
73You want to be able to connect Mojo interfaces between these two processes.
74Rejoice, because this section was written just for you.
75
76NOTE: For legacy reasons, some API terminology may refer to concepts of "parent"
77and "child" as a relationship between processes being connected by Mojo. This
78relationship is today completely orthogonal to any notion of process hierarchy
79in the OS, and so use of these APIs is not constrained by an adherence to any
80such hierarchy.
81
82Mojo requires you to bring your own OS pipe to the party, and it will do the
83rest. It also provides a convenient mechanism for creating such pipes, known as
84a `PlatformChannelPair`.
85
86You provide one end of this pipe to the EDK in the local process via
87`PendingProcessConnection` - which can also be used to create cross-process
88message pipes (see the next section) - and you're responsible for getting the
89other end into the remote process.
90
91```
92#include "base/process/process_handle.h"
93#include "base/threading/thread.h"
94#include "mojo/edk/embedder/embedder.h"
95#include "mojo/edk/embedder/pending_process_connection.h"
96#include "mojo/edk/embedder/platform_channel_pair.h"
97#include "mojo/edk/embedder/scoped_ipc_support.h"
98
99// You write this. It launches a new process, passing the pipe handle
100// encapsulated by |channel| by any means possible (e.g. on Windows or POSIX
101// you may inhert the file descriptor/HANDLE at launch and pass a commandline
102// argument to indicate its numeric value). Returns the handle of the new
103// process.
104base::ProcessHandle LaunchCoolChildProcess(
105 mojo::edk::ScopedPlatformHandle channel);
106
107int main(int argc, char** argv) {
108 mojo::edk::Init();
109
110 base::Thread ipc_thread("ipc!");
111 ipc_thread.StartWithOptions(
112 base::Thread::Options(base::MessageLoop::TYPE_IO));
113
114 mojo::edk::ScopedIPCSupport ipc_support(
115 ipc_thread.task_runner(),
116 mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN);
117
118 // This is essentially always an OS pipe (domain socket pair, Windows named
119 // pipe, etc.)
120 mojo::edk::PlatformChannelPair channel;
121
122 // This is a scoper which encapsulates the intent to connect to another
123 // process. It exists because process connection is inherently asynchronous,
124 // things may go wrong, and the lifetime of any associated resources is bound
125 // by the lifetime of this object regardless of success or failure.
126 mojo::edk::PendingProcessConnection child;
127
128 base::ProcessHandle child_handle =
129 LaunchCoolChildProcess(channel.PassClientHandle());
130
131 // At this point it's safe for |child| to go out of scope and nothing will
132 // break.
133 child.Connect(child_handle, channel.PassServerHandle());
134
135 return 0;
136}
137```
138
139The launched process code uses `SetParentPipeHandle` to get connected, and might
140look something like:
141
142```
143#include "base/threading/thread.h"
144#include "mojo/edk/embedder/embedder.h"
145#include "mojo/edk/embedder/scoped_ipc_support.h"
146
147// You write this. It acquires the ScopedPlatformHandle that was passed by
148// whomever launched this process (i.e. LaunchCoolChildProcess above).
149mojo::edk::ScopedPlatformHandle GetChannelHandle();
150
151int main(int argc, char** argv) {
152 mojo::edk::Init();
153
154 base::Thread ipc_thread("ipc!");
155 ipc_thread.StartWithOptions(
156 base::Thread::Options(base::MessageLoop::TYPE_IO));
157
158 mojo::edk::ScopedIPCSupport ipc_support(
159 ipc_thread.task_runner(),
160 mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN);
161
162 mojo::edk::SetParentPipeHandle(GetChannelHandle());
163
164 return 0;
165}
166```
167
168Now you have IPC initialized between two processes. For some practical examples
169of how this is done, you can dig into the various multiprocess tests in the
170`mojo_system_unittests` test suite.
171
172## Bootstrapping Cross-Process Message Pipes
173
174Having internal Mojo IPC support initialized is pretty useless if you don't have
175any message pipes spanning the process boundary. Fortunately, this is made
176trivial by the EDK: `PendingProcessConnection` has a
177`CreateMessagePipe` method which synthesizes a new solitary message pipe
178endpoint for your immediate use, while also generating a magic token string that
179can be exchanged for the other end of the pipe via
180`mojo::edk::CreateChildMessagePipe`.
181
182The token exchange can be done by the same process (which is sometimes useful),
183or by the process that is eventually connected via `Connect()` on that
184`PendingProcessConnection`. This means that you can effectively pass message
185pipes on the commandline by passing a token string.
186
187We can modify our existing sample code as follows:
188
189```
190#include "base/command_line.h"
191#include "base/process/process_handle.h"
192#include "base/threading/thread.h"
193#include "mojo/edk/embedder/embedder.h"
194#include "mojo/edk/embedder/pending_process_connection.h"
195#include "mojo/edk/embedder/platform_channel_pair.h"
196#include "mojo/edk/embedder/scoped_ipc_support.h"
197#include "mojo/public/cpp/system/message_pipe.h"
198#include "local/foo.mojom.h" // You provide this
199
200base::ProcessHandle LaunchCoolChildProcess(
201 const base::CommandLine& command_line,
202 mojo::edk::ScopedPlatformHandle channel);
203
204int main(int argc, char** argv) {
205 mojo::edk::Init();
206
207 base::Thread ipc_thread("ipc!");
208 ipc_thread.StartWithOptions(
209 base::Thread::Options(base::MessageLoop::TYPE_IO));
210
211 mojo::edk::ScopedIPCSupport ipc_support(
212 ipc_thread.task_runner(),
213 mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN);
214
215 mojo::edk::PlatformChannelPair channel;
216
217 mojo::edk::PendingProcessConnection child;
218
219 base::CommandLine command_line; // Assume this is appropriately initialized
220
221 // Create a new message pipe with one end being retrievable in the new
222 // process. Note that it doesn't matter whether we call CreateMessagePipe()
223 // before or after Connect(), and we can create as many different pipes as
224 // we like.
225 std::string pipe_token;
226 mojo::ScopedMessagePipeHandle my_pipe = child.CreateMessagePipe(&pipe_token);
227 command_line.AppendSwitchASCII("primordial-pipe", pipe_token);
228
229 base::ProcessHandle child_handle =
230 LaunchCoolChildProcess(command_line, channel.PassClientHandle());
231
232 child.Connect(child_handle, channel.PassServerHandle());
233
234 // We can start using our end of the pipe immediately. Here we assume the
235 // other end will eventually be bound to a local::mojom::Foo implementation,
236 // so we can start making calls on that interface.
237 //
238 // Note that this could even be done before the child process is launched and
239 // it would still work as expected.
240 local::mojom::FooPtr foo;
241 foo.Bind(local::mojom::FooPtrInfo(std::move(my_pipe), 0));
242 foo->DoSomeStuff(42);
243
244 return 0;
245}
246```
247
248and for the launched process:
249
250
251```
252#include "base/command_line.h"
253#include "base/run_loop/run_loop.h"
254#include "base/threading/thread.h"
255#include "mojo/edk/embedder/embedder.h"
256#include "mojo/edk/embedder/scoped_ipc_support.h"
257#include "mojo/public/cpp/bindings/binding.h"
258#include "mojo/public/cpp/system/message_pipe.h"
259#include "local/foo.mojom.h" // You provide this
260
261mojo::edk::ScopedPlatformHandle GetChannelHandle();
262
263class FooImpl : local::mojom::Foo {
264 public:
265 explicit FooImpl(local::mojom::FooRequest request)
266 : binding_(this, std::move(request)) {}
267 ~FooImpl() override {}
268
269 void DoSomeStuff(int32_t n) override {
270 // ...
271 }
272
273 private:
274 mojo::Binding<local::mojom::Foo> binding_;
275
276 DISALLOW_COPY_AND_ASSIGN(FooImpl);
277};
278
279int main(int argc, char** argv) {
280 base::CommandLine::Init(argc, argv);
281
282 mojo::edk::Init();
283
284 base::Thread ipc_thread("ipc!");
285 ipc_thread.StartWithOptions(
286 base::Thread::Options(base::MessageLoop::TYPE_IO));
287
288 mojo::edk::ScopedIPCSupport ipc_support(
289 ipc_thread.task_runner(),
290 mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN);
291
292 mojo::edk::SetParentPipeHandle(GetChannelHandle());
293
294 mojo::ScopedMessagePipeHandle my_pipe = mojo::edk::CreateChildMessagePipe(
295 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
296 "primordial-pipe"));
297
298 local::mojom::FooRequest foo_request;
299 foo_request.Bind(std::move(my_pipe));
300 FooImpl impl(std::move(foo_request));
301
302 // Run forever!
303 base::RunLoop().Run();
304
305 return 0;
306}
307```
308
309Note that the above samples assume an interface definition in
310`//local/test.mojom` which would look something like:
311
312```
313module local.mojom;
314
315interface Foo {
316 DoSomeStuff(int32 n);
317};
318```
319
320Once you've bootstrapped your process connection with a real mojom interface,
321you can avoid any further mucking around with EDK APIs or raw message pipe
322handles, as everything beyond this point - including the passing of other
323interface pipes - can be handled eloquently using public bindings APIs.
324
325See [additional Mojo documentation](
326 https://www.chromium.org/developers/design-documents/mojo) for more
327information.
328