Mojo Embedder Development Kit (EDK)
この文書はMojoドキュメント化のサブセット
概観
MojoEDKは(バイナリ不安定な)APIで、これを使うとプロセス間での通信ができる。
//mojo/edk/embedderにあるAPIサーフェスを使うには(混乱をもたらすようだが)GNターゲットの//mojo/edk/systemの直接依存が必要になる。それにもかかわらず、mojo/edi/systemにあるヘッダを直接参照すべきではない。ここにあるのはEDK内部の詳細であるから。
注意:あなたが新しいバイナリのエントリポイントを作り込むのでない限り(例えばmain()をもつ新しい実行形式をつくる)、EDK APIについて何も知っている必要はない。Chromeレポで定義されるほとんどのプロセスは、すでにEDKを初期化しており、Mojoの他の公開APIは"即座に動く"。
基本的初期化
プロセスでMojoを使うには、mojo::edk::Initを一回だけ呼ぶ。
#include "mojo/edk/embedder/embedder.h" int main(int argc, char** argv) { mojo::edk::Init(); // ここから、メッセージパイプの作成やメッセージの書き込みなどができる。 return 0; }
IPCサポートがないとほとんど使えないので、次にこれを初期化する。
IPC初期化
バックグランドで動くTaskRunnerが必要で、それがIOを監視する。
以下がIPCのためのバックグランドスレッド作成とそれを使うMojoのコードである。Chromiumではすでに存在するブラウザプロセス内のあるいはコンテント子プロセス内のIOスレッドを利用する。
#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, 0)); // このオプジェクトが存在する間、IPC接続に関連するすべてのEDK APIを使うことができる。 // プロセス境界にまたがるメッセージパイプも機能し続ける。 mojo::edk::ScopedIPCSupport ipc_support( ipc_thread.task_runner(), mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); return 0; }
これでプロセスはMojoIPCに対しての完全な準備ができた。
Chromiumのすべてのプロセスではこのセットアップをスタートアップの初期に行う。
2つのプロセスを接続
MojoIPCの準備ができたら、他のプロセスを起動してそのプロセスもMojoIPCの準備をしないとならない。そのためのコードを記述する。
歴史的理由により、接続されるプロセスを呼ぶためにいくつかのAPIの用語がParentやChildになっているがこの関係はOSのプロセス階層とは完全に直角(orthogonal)である。なのでこれらAPIはそのようなOS階層に制約されない。
MojoにはOSのパイプを提供しなければならない。それ以外のことはMojoがやる。PlatformChannelPairをつかってパイプを作成することもできる。
パイプの端点の1つはOutgoingBrokerClientInvitationを経由してEDKに提供すること。OutgoingBrokerClientInvitationはプロセス間のメッセージパイプ作成に使うこともできる(次節参照)そしてもう1つの端点をリモートプロセスに送らなければならない。
#include "base/process/process_handle.h" #include "base/threading/thread.h" #include "mojo/edk/embedder/embedder.h" #include "mojo/edk/embedder/outgoing_broker_client_invitation.h" #include "mojo/edk/embedder/platform_channel_pair.h" #include "mojo/edk/embedder/scoped_ipc_support.h" // あなたがこれを書く。これは新規プロセスを起動し、|チャネル|にカプセル化された // パイプハンドルを渡す(WindowsやPOSIXでは起動時にファイルデスクリプタやHANDLE // を継承しその数値をコマンドラインで渡す)。戻り値は新規プロセスのハンドル 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, 0)); mojo::edk::ScopedIPCSupport ipc_support( ipc_thread.task_runner(), mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); // これは本質的に常にOSパイプ(ドメインソケットペア、Windows名前付き // パイプ、その他) mojo::edk::PlatformChannelPair channel; // これはscoper、他のプロセスへの接続要求をカプセル化。 // プロセス接続は非同期のためリセーすのライフタイム管理は // このオブジェクトで行う。 mojo::edk::OutgoingBrokerClientInvitation invitation; base::ProcessHandle child_handle = LaunchCoolChildProcess(channel.PassClientHandle()); // この時点で|child|はスコープから外れて良い。なにも壊れない child.Connect(child_handle, channel.PassServerHandle()); return 0; }
起動されたプロセスコードはIncomingBrokerClientInvitationを使って接続する、以下のようになる。
#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, 0)); mojo::edk::ScopedIPCSupport ipc_support( ipc_thread.task_runner(), mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); mojo::edk::IncomingBrokerClientInvitation::Accept(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: OutgoingBrokerClientInvitation has an AttachMessagePipe method which synthesizes a new solitary message pipe endpoint for your immediate use, and attaches the other end to the invitation such that it can later be extracted by name by the invitee from the IncomingBrokerClientInvitation.
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/outgoing_broker_client_invitation.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, 0)); mojo::edk::ScopedIPCSupport ipc_support( ipc_thread.task_runner(), mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); mojo::edk::PlatformChannelPair channel; mojo::edk::OutgoingBrokerClientInvitation invitation; // Create a new message pipe with one end being retrievable in the new // process. Note that the name chosen for the attachment is arbitrary and // scoped to this invitation. mojo::ScopedMessagePipeHandle my_pipe = invitation.AttachMessagePipe("pretty_cool_pipe"); base::ProcessHandle child_handle = LaunchCoolChildProcess(channel.PassClientHandle()); invitation.Send( child_handle, mojo::edk::ConnectionParams(mojo::edk::TransportProtocol::kLegacy, 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/run_loop/run_loop.h" #include "base/threading/thread.h" #include "mojo/edk/embedder/embedder.h" #include "mojo/edk/embedder/incoming_broker_client_invitation.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) { 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); auto invitation = mojo::edk::IncomingBrokerClientInvitation::Accept( mojo::edk::ConnectionParams(mojo::edk::TransportProtocol::kLegacy, GetChannelHandle())); mojo::ScopedMessagePipeHandle my_pipe = invitation->ExtractMessagePipe("pretty_cool_pipe"); FooImpl impl(local::mojom::FooRequest(std::move(my_pipe))); // 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.
Setting System Properties
The public Mojo C System API exposes a MojoGetProperty function for querying global, embedder-defined property values. These can be set by calling:
mojo::edk::SetProperty(MojoPropertyType type, const void* value)