1 # Mojo Embedder Development Kit (EDK) 2 3 The Mojo EDK is a (binary-unstable) API which enables a process to use Mojo both 4 internally and for IPC to other Mojo-embedding processes. 5 6 Using any of the API surface in `//mojo/edk/embedder` requires (somewhat 7 confusingly) a direct dependency on the GN `//mojo/edk/system` target. Despite 8 this fact, you should never reference any of the headers in `mojo/edk/system` 9 directly, as everything there is considered to be an internal detail of the EDK. 10 11 ## Basic Initialization 12 13 In 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 19 int 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 28 As it happens though, Mojo is less useful without some kind of IPC support as 29 well, and that's a second initialization step. 30 31 ## IPC Initialization 32 33 You also need to provide the system with a background TaskRunner on which it can 34 watch for inbound I/O from any of the various other processes you will later 35 connect to it. 36 37 Here we'll just create a new background thread for IPC and let Mojo use that. 38 Note that in Chromium, we use the existing "IO thread" in the browser process 39 and 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 46 int 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 64 This process is now fully prepared to use Mojo IPC! 65 66 Note that all existing process types in Chromium already perform this setup 67 very early during startup. 68 69 ## Connecting Two Processes 70 71 Now suppose you're running a process which has initialized Mojo IPC, and you 72 want to launch another process which you know will also initialize Mojo IPC. 73 You want to be able to connect Mojo interfaces between these two processes. 74 Rejoice, because this section was written just for you. 75 76 NOTE: For legacy reasons, some API terminology may refer to concepts of "parent" 77 and "child" as a relationship between processes being connected by Mojo. This 78 relationship is today completely orthogonal to any notion of process hierarchy 79 in the OS, and so use of these APIs is not constrained by an adherence to any 80 such hierarchy. 81 82 Mojo requires you to bring your own OS pipe to the party, and it will do the 83 rest. It also provides a convenient mechanism for creating such pipes, known as 84 a `PlatformChannelPair`. 85 86 You 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 88 message pipes (see the next section) - and you're responsible for getting the 89 other 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. 104 base::ProcessHandle LaunchCoolChildProcess( 105 mojo::edk::ScopedPlatformHandle channel); 106 107 int 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 139 The launched process code uses `SetParentPipeHandle` to get connected, and might 140 look 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). 149 mojo::edk::ScopedPlatformHandle GetChannelHandle(); 150 151 int 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 168 Now you have IPC initialized between two processes. For some practical examples 169 of 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 174 Having internal Mojo IPC support initialized is pretty useless if you don't have 175 any message pipes spanning the process boundary. Fortunately, this is made 176 trivial by the EDK: `PendingProcessConnection` has a 177 `CreateMessagePipe` method which synthesizes a new solitary message pipe 178 endpoint for your immediate use, while also generating a magic token string that 179 can be exchanged for the other end of the pipe via 180 `mojo::edk::CreateChildMessagePipe`. 181 182 The token exchange can be done by the same process (which is sometimes useful), 183 or by the process that is eventually connected via `Connect()` on that 184 `PendingProcessConnection`. This means that you can effectively pass message 185 pipes on the commandline by passing a token string. 186 187 We 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 200 base::ProcessHandle LaunchCoolChildProcess( 201 const base::CommandLine& command_line, 202 mojo::edk::ScopedPlatformHandle channel); 203 204 int 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 248 and 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 261 mojo::edk::ScopedPlatformHandle GetChannelHandle(); 262 263 class 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 279 int 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 309 Note that the above samples assume an interface definition in 310 `//local/test.mojom` which would look something like: 311 312 ``` 313 module local.mojom; 314 315 interface Foo { 316 DoSomeStuff(int32 n); 317 }; 318 ``` 319 320 Once you've bootstrapped your process connection with a real mojom interface, 321 you can avoid any further mucking around with EDK APIs or raw message pipe 322 handles, as everything beyond this point - including the passing of other 323 interface pipes - can be handled eloquently using public bindings APIs. 324 325 See [additional Mojo documentation]( 326 https://www.chromium.org/developers/design-documents/mojo) for more 327 information. 328