Chromium进程间通信Mojo bindings

我们之前使用Mojo的方法是通过读写原始的buffer,还是比较原始的。Chromium里面使用了更上层的bindings接口来进行IPC通信。它先定义一个mojom的接口文件,然后生成相关的接口cpp代码。发送方调用cpp代码接口,接收方去实现cpp代码接口。这种用法类似Protocol Buffers。我下面以一个计算加法的接口要演示如果使用bindings。

mojom 接口文件

首先定义一个接口文件math.mojom,其内容如下:

module math.mojom;

interface Math {
  Add(int32 x, int32 y) => (int64 sum);
};

Add接口需要两个int32的参数,然后返回int64的结果,这个接口会生成相应的cpp接口代码如下:

class  Math
    : public MathInterfaceBase {
 public:
  static const char Name_[];
  static constexpr uint32_t Version_ = 0;
  static constexpr bool PassesAssociatedKinds_ = false;
  static constexpr bool HasSyncMethods_ = false;

  using Base_ = MathInterfaceBase;
  using Proxy_ = MathProxy;

  template <typename ImplRefTraits>
  using Stub_ = MathStub<ImplRefTraits>;

  using RequestValidator_ = MathRequestValidator;
  using ResponseValidator_ = MathResponseValidator;
  enum MethodMinVersions : uint32_t {
    kAddMinVersion = 0,
  };
  virtual ~Math() {}


  using AddCallback = base::OnceCallback<void(int64_t)>;

  virtual void Add(int32_t x, int32_t y, AddCallback callback) = 0;
};

可以看到,返回结果是通过AddCallback回调函数传递的。

调用方

调用方的接口如下:

void OnAddResult(int64_t result) {
  std::cout << "add result:" << result << std::endl;
}

int main(int argc, char* argv[]) {
  base::AtExitManager exit_manager;
  base::CommandLine::Init(0, 0);
  logging::LoggingSettings settings;
  settings.logging_dest = logging::LOG_TO_ALL;
  settings.delete_old = logging::DELETE_OLD_LOG_FILE;
  settings.log_file_path = L"mojo.log";
  logging::InitLogging(settings);

  base::MessageLoop loop;

  mojo::core::Init();
  base::Thread ipc_thread("ipc");
  ipc_thread.StartWithOptions(
      base::Thread::Options(base::MessagePumpType::IO, 0));
  mojo::core::ScopedIPCSupport ipc_support(
      ipc_thread.task_runner(),
      mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);

  mojo::PlatformChannelEndpoint endpoint =
      mojo::NamedPlatformChannel::ConnectToServer(L"test");
  mojo::ScopedMessagePipeHandle pipe =
      mojo::IncomingInvitation::AcceptIsolated(std::move(endpoint));

  mojo::Remote<math::mojom::Math> caller(
      mojo::PendingRemote<math::mojom::Math>(std::move(pipe), 0));
  caller->Add(1, 2, base::BindOnce(OnAddResult));

  base::RunLoop().Run();

  return 0;
}

建立IPC连接的代码跟之前类似,但是我们不再需要自己手动的传输原始buffer数据了,而是定义一个mojo::Remote<math::mojom::Math> caller,把pipe传进去,然后就可以直接调用了caller->Add(1, 2, base::BindOnce(OnAddResult));,这就完成了数据的发送。而后接口返回的结果会回调给OnAddResult

接收方

class MathImpl : public math::mojom::Math {
 public:
  explicit MathImpl(mojo::PendingReceiver<math::mojom::Math> receiver)
      : receiver_(this, std::move(receiver)) {}

  void Add(int32_t x, int32_t y, AddCallback reply) override {
    std::move(reply).Run(static_cast<int64_t>(x) + y);
  }

 private:
  mojo::Receiver<math::mojom::Math> receiver_;
};

int main(int argc, char* argv[]) {
  base::AtExitManager exit_manager;
  base::CommandLine::Init(0, 0);
  logging::LoggingSettings settings;
  settings.logging_dest = logging::LOG_TO_ALL;
  settings.delete_old = logging::DELETE_OLD_LOG_FILE;
  settings.log_file_path = L"mojo.log";
  logging::InitLogging(settings);

  base::MessageLoop loop;

  mojo::core::Init();
  base::Thread ipc_thread("ipc");
  ipc_thread.StartWithOptions(
      base::Thread::Options(base::MessagePumpType::IO, 0));
  mojo::core::ScopedIPCSupport ipc_support(
      ipc_thread.task_runner(),
      mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);

  mojo::NamedPlatformChannel::Options options;
  options.server_name = L"test";
  mojo::NamedPlatformChannel named_channel(options);
  mojo::ScopedMessagePipeHandle pipe = mojo::OutgoingInvitation::SendIsolated(
      named_channel.TakeServerEndpoint(), nullptr);

  MathImpl math(mojo::PendingReceiver<math::mojom::Math>(std::move(pipe)));

  base::RunLoop().Run();

  return 0;
}

接收方稍微复杂一些,它需要实现math::mojom::Math。然后创建一个实例MathImpl math(mojo::PendingReceiver<math::mojom::Math>(std::move(pipe)));,当发送方发送数据的时候,会自动的调用到MathImpl::Add

通过接口发送接口

可以从上面例子看到,跨进程的创建接口需要一个pipe。但是我们创建IPC连接,附加的pipe通常是有限的。如果要创建任意多个接口,就需要任意多个pipe。之前说过Mojo的pipe可以发送各种类型的数据,因此我们可以通过一个已经建立好连接的接口去发送其他接口所需要的pipe数据。

mojom定义文件里支持handle<message_pipe>pending_remote<InterfaceType>pending_receiver<InterfaceType>等参数类型,可以传输pipe相关数据。

module example.mojom;

interface InterfaceProvider {
  GetInterface(string interface_name, handle<message_pipe> pipe);
};

interface Foo {
  Ping(string name);
};

interface Bar {
  Ping(int32 name);
};

我们在InterfaceProvider里定义一个GetInterface接口,可以通过这个接口发送其他接口数据,从而辅助创建一个新的接口。

调用方

  mojo::Remote<example::mojom::InterfaceProvider> provider(
      mojo::PendingRemote<example::mojom::InterfaceProvider>(std::move(pipe),
                                                             0));
  mojo::Remote<example::mojom::Foo> foo;
  provider->GetInterface(foo->Name_,
                         foo.BindNewPipeAndPassReceiver().PassPipe());
  foo->Ping("mojo");

  for (int i = 0; i < 10; i++) {
    mojo::Remote<example::mojom::Bar> bar;
    provider->GetInterface(bar->Name_,
                           bar.BindNewPipeAndPassReceiver().PassPipe());
    bar->Ping(i);
  }

我们首先建立好InterfaceProvider的连接,然后其他接口通过GetInterface把自己的接口名和pipe句柄发送个接收方。

这样就可以直接调用接口了。

接收方

class FooImpl : public example::mojom::Foo {
 public:
  explicit FooImpl(mojo::PendingReceiver<example::mojom::Foo> receiver)
      : receiver_(this, std::move(receiver)) {}
  ~FooImpl() override {}

  void Ping(const std::string& name) override {
    std::cout << "hello " << name << std::endl;
  }

 private:
  mojo::Receiver<example::mojom::Foo> receiver_;
};

class BarImpl : public example::mojom::Bar {
 public:
  explicit BarImpl(mojo::PendingReceiver<example::mojom::Bar> receiver)
      : receiver_(this, std::move(receiver)) {}
  ~BarImpl() override {}

  void Ping(int32_t name) override {
    std::cout << "hello " << name << std::endl;
  }

 private:
  mojo::Receiver<example::mojom::Bar> receiver_;
};

class InterfaceProviderImpl : public example::mojom::InterfaceProvider {
 public:
  explicit InterfaceProviderImpl(
      mojo::PendingReceiver<example::mojom::InterfaceProvider> receiver)
      : receiver_(this, std::move(receiver)) {}

  void GetInterface(const std::string& interface_name,
                    mojo::ScopedMessagePipeHandle pipe) override {
    if (interface_name == std::string(FooImpl::Name_)) {
      mojo::PendingReceiver<example::mojom::Foo> receiver(std::move(pipe));
      foo_.reset(new FooImpl(std::move(receiver)));
    } else if (interface_name == std::string(BarImpl::Name_)) {
      mojo::PendingReceiver<example::mojom::Bar> receiver(std::move(pipe));
      std::unique_ptr<BarImpl> bar(new BarImpl(std::move(receiver)));
      bars_.emplace_back(std::move(bar));
    }
  }

 private:
  std::unique_ptr<FooImpl> foo_;
  std::vector<std::unique_ptr<BarImpl>> bars_;
  mojo::Receiver<example::mojom::InterfaceProvider> receiver_;
};

  InterfaceProviderImpl provider(
      mojo::PendingReceiver<example::mojom::InterfaceProvider>(
          std::move(pipe)));

接收方在GetInterface里面收到接口的数据,根据名称生成对应的接口实现即可。

接口的生命周期管理

调用方创建一个实例就可以调用接口,不必等待接收方创建完成。

  {
    mojo::Remote<example::mojom::Lifetime> caller(
        mojo::PendingRemote<example::mojom::Lifetime>(std::move(pipe), 0));
    caller->Ping(1);
  }

接收方按需创建好之后,可以使用set_disconnect_handler来管理自己的生命周期,当接口断开就删除自己。

    receiver_.set_disconnect_handler(
        base::BindOnce(&LifetimeImpl::DeleteSelf, base::Unretained(this)));
  static void DeleteSelf(LifetimeImpl* self) { delete self; }

如果调用和接收双方长期保持生命周期,也可以在一个接口上重复调用多次。