Bootstrap

chromium mojo 快速入门

术语:

消息管道:是一组端点. 每个endpoint有一个接受消息的队列, 在一个端点上写消息会高效地放入对端端点的消息队列上。所以消息管道是双工通信的。

mojom文件:描述了接口它们描述了类似proto files的强类型消息结构,通过binding generator可以产生对应于不同语言的文件。

给定一个mojom interface和一条message pipe, 它的两个端点可以打上InterfacePtrBinding的标签。现在它描述了一条这样的message pipe,它发送由mojom interface描述的消息。

InterfacePtr:是发送消息的端点,一旦绑定到一个消息管道的端点,就可以马上序列化要发送的消息,并写入管道

InterfaceRequest:是接受消息的端点,本质上仅仅是一个持有消息管道端点的容器,本身不会做任何事情,需要传递到直到绑定了实现了mojom文件接口的类,才能读取消息。


考虑如下的mojom文件:

module sample.mojom;

interface Logger {
  Log(string message);
};

通过mojo的binding generator可以产生以下的logging.mojom.h的c++文件:

namespace sample {
namespace mojom {

class Logger {
  virtual ~Logger() {}

  virtual void Log(const std::string& message) = 0;
};

using LoggerPtr = mojo::InterfacePtr<Logger>;
using LoggerRequest = mojo::InterfaceRequest<Logger>;

}  // namespace mojom
}  // namespace sample

 首先需要创建消息管道,通常调用MakeRequest创建:

#include "sample/logger.mojom.h"

sample::mojom::LoggerPtr logger;
auto request = mojo::MakeRequest(&logger);

MakeRequset的如下: 

template <typename Interface>
InterfaceRequest<Interface> MakeRequest(
    InterfacePtr<Interface>* ptr,
    scoped_refptr<base::SingleThreadTaskRunner> runner = nullptr) {
  MessagePipe pipe;
  ptr->Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0), 0u),
            std::move(runner));
  return InterfaceRequest<Interface>(std::move(pipe.handle1));
}

可见创建消息管道的同时,InterfacePtr端已经绑定到了消息管道的一端,也就是此时,InterfacePtr已经可以向消息管道发送消息了,当然对端还未绑定,发送的消息会堆积在对端。 

接下来就需要绑定InterfaceRequest端,即LoggerRequest,一旦绑定,就会安排一个task去读取、反序列化并分化所有可读的消息给mojom文件中接口实现类。例如接口类实现如下:

#include "base/logging.h"
#include "base/macros.h"
#include "sample/logger.mojom.h"

class LoggerImpl : public sample::mojom::Logger {
 public:
  // NOTE: A common pattern for interface implementations which have one
  // instance per client is to take an InterfaceRequest in the constructor.

  explicit LoggerImpl(sample::mojom::LoggerRequest request)
      : binding_(this, std::move(request)) {}
  ~Logger() override {}

  // sample::mojom::Logger:
  void Log(const std::string& message) override {
    LOG(ERROR) << "[Logger] " << message;
  }

 private:
  mojo::Binding<sample::mojom::Logger> binding_;

  DISALLOW_COPY_AND_ASSIGN(LoggerImpl);
};

此时我们可以使用前面构造的LoggerRequest构造一个LoggerImpl,一但构造完成,消息就会尽可能快地发送给LoggerImpl。最终就可以在实现类的一端看到Logger发送的“hello”。

总的流程可以概括如下:


当然有时候发送端可能期望有一个回复,此时mojom接口可以调整为

module sample.mojom;

interface Logger {
  Log(string message);
  GetTail() => (string message);
};

其中,Log中的message参数和GetTail后置的message都是会被序列化进管道传送的消息。对应的C++文件如下:

namespace sample {
namespace mojom {

class Logger {
 public:
  virtual ~Logger() {}

  virtual void Log(const std::string& message) = 0;

  using GetTailCallback = base::OnceCallback<void(const std::string& message)>;

  virtual void GetTail(GetTailCallback callback) = 0;
}

}  // namespace mojom
}  // namespace sample

和不需要回复的版本一样,发送端调用的接口,在实现端也会对应的被调用,而其中的GetTail,发送端发送一个回调函数的参数以接受回复。

接受端的实现类如下

class LoggerImpl : public sample::mojom::Logger {
 public:
  // NOTE: A common pattern for interface implementations which have one
  // instance per client is to take an InterfaceRequest in the constructor.

  explicit LoggerImpl(sample::mojom::LoggerRequest request)
      : binding_(this, std::move(request)) {}
  ~Logger() override {}

  // sample::mojom::Logger:
  void Log(const std::string& message) override {
    LOG(ERROR) << "[Logger] " << message;
    lines_.push_back(message);
  }

  void GetTail(GetTailCallback callback) override {
    std::move(callback).Run(lines_.back());
  }

 private:
  mojo::Binding<sample::mojom::Logger> binding_;
  std::vector<std::string> lines_;

  DISALLOW_COPY_AND_ASSIGN(LoggerImpl);

那么,在发送端发送过消息后,即调用过Log后,继续如下调用GetTail

void OnGetTail(const std::string& message) {
  LOG(ERROR) << "Tail was: " << message;
}

logger->GetTail(base::BindOnce(&OnGetTail));

那么在实现端调用了GetTail后,将会将line_.back()序列化并发送回发送端,同时遵循发送端的内部逻辑,触发回调函数得到发送过去的最后一条消息。 


当然在browser和renderer通信时, browser其实通过消息管道连接到Service Manager,由它统一管理和render进程的连接。此时需要在browser的manifet文件renderer的manifest文件中申明,如下

content_renderer_manifest.json:
...
  "interface_provider_specs": {
    "service_manager:connector": {
      "provides": {
        "cool_ping_feature": [
          "sample::mojom::Logger"
        ]
      },
    },
...
content_browser_manifest.json:
...
 "interface_provider_specs": {
    "service_manager:connector": {
      "requires": {
        "content_renderer": [ "cool_ping_feature" ],
      },
    },
  },
...

在renderer端则可以如下实现接口类,并通常在Init时注册到Service Manager,

class LoggerImpl : mojom::Logger {
  void BindToInterface(example::mojom::LoggerRequest request) {
    binding_.reset(
      new mojo::Binding<mojom::MemlogClient>(this, std::move(request)));
  }
  void Log(const std::string& message) override {
    LOG(ERROR) << "[Logger] " << message;
    lines_.push_back(message);
  }
  void GetTail(LoggerCallback callback) { std::move(callback).Run(4); }
  std::unique_ptr<mojo::Binding<mojom::Logger>> binding_;
};

RenderThreadImpl::Init() {
...
  this->logger = std::make_unique<LoggerImpl>();
  auto registry = base::MakeUnique<service_manager::BinderRegistry>();

  // This makes the assumption that |this->logger| will outlive |registry|.
  registry->AddInterface(base::Bind(&LoggerImpl::BindToInterface), base::Unretained(this->Logger.get()));

  GetServiceManagerConnection()->AddConnectionFilter(
      base::MakeUnique<SimpleConnectionFilter>(std::move(registry)));
...
}

那么此时,browser端可以如下建立与renderer的连接,通常有如下两种方式

sample::mojom::LoggerPtr logger;  // Make sure to keep this alive! Otherwise the response will never be received.
sample::mojom::LoggerRequest request = mojo::MakeRequest(&logger);
logger->GetTail(base::BindOnce(&OnGetTail));
content::RenderProcessHost* host = GetRenderProcessHost();
content::BindInterface(host, std::move(request));

其中content::BindInterface是一个辅助接口,通过Service Manager通过postTask将request传送给renderer,创建对应的LoggerImpl,完成连接。

或者

sample::mojom::LoggerPtr logger;
render_frame->GetRemoteInterfaces()->GetInterface(mojo::MakeRequest(&logger));
logger->GetTail(base::BindOnce(&OnGetTail));

RenderProcessHost作为sender,RenderProcess为receiver的例子如下, 

void RenderProcessHostImpl::InitializeChannelProxy() {
  ...
  content::BindInterface(this, &child_control_interface_);
  ...
}


template <typename Host, typename Interface>
void BindInterface(Host* host, mojo::InterfacePtr<Interface>* ptr) {
  mojo::MessagePipe pipe;
  ptr->Bind(mojo::InterfacePtrInfo<Interface>(std::move(pipe.handle0), 0u));
  host->BindInterface(Interface::Name_, std::move(pipe.handle1));
}

renderer和browser建立通信,更详细的可以看这个帖子

Mojo in Chromium

Mojo C++ System API

Mojo C++ Bindings API

Understanding mojo behavior

;