Mojo 概述
一个消息管道(pipe
)是一对端点(endpoints
)。每个端点都有一个传入消息的队列,在一个端点上写一条消息可以有效地将该消息排队到另一个(对等)端点上。因此,消息管道是双向的。
mojom
文件描述了接口,接口是消息的强类型集合。对于熟悉Google protobufs的开发人员来说,每个接口消息大致类似于单个proto消息。
给定mojom接口和消息管道,可以将其中一个端点指定为 Remote
,并用于发送接口描述的消息。另一个端点可以指定为 Receiver
,用于接收接口消息。
上面的概括有点过于简单化了。请记住,消息管道仍然是双向的,mojom 消息可能需要回复。回复从
Receiver
端发送,并由Remote
端点接收。
为了处理接收到的消息,Receiver
端点必须与其 mojom 接口的实现相关联(即 bound
)。
示例
假设我们要在浏览器进程中将 “Ping” 消息从呈现帧(render frame)发送到其对应的 RenderFrameHostImpl
实例。为此,我们需要定义一个 mojom 接口,创建一个使用该接口的管道,然后将管道的一端 放置到正确的位置,以便在那里接收和处理发送的消息。
定义接口
首先创建一个定义接口的文件,后缀是 .mojom
// src/example/public/mojom/ping_responder.mojom
module example.mojom;
interface PingResponder {
// Receives a "Ping" and responds with a random integer.
Ping() => (int32 random);
};
然后,需要在规则文件中定义这个文件用于生成c++代码
# src/example/public/mojom/BUILD.gn
import("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
sources = [ "ping_responder.mojom" ]
}
创建管道
作为一般规则和使用Mojo时的便利,接口的 client(即
Remote
端)通常是创建新管道的一方。这很方便,因为Remote
可以用于立即开始发送消息,而无需等待 InterfaceRequest 端点在任何地方被传输或绑定。
以下代码编写在 render 中的某块
// src/third_party/blink/example/public/ping_responder.h
mojo::Remote ping_responder;
mojo::PendingReceiver receiver =
ping_responder.BindNewPipeAndPassReceiver();
在这个例子中,ping_responder
是 Remote
,并且 receiver
是 PendingReceiver
是 Receiver
的前身,最终会变成一个 Receiver
。BindNewPipeAndPassReceiver
是创建消息管道的最常用方法:它将 PendingReceiver
作为返回值。
注意:
PendingReceiver
实际上什么也做不了。它是一个消息管道端点(endpoint)的惰性保持器。它的存在只是为了在编译时使其端点更强类型化,示意端点希望由相同接口类型的Receiver
绑定。
发送消息
在我们的 Remote
中调用 Ping()
方法发送消息
// src/third_party/blink/example/public/ping_responder.h
ping_responder->Ping(base::BindOnce(&OnPong));
重要:如果我们想要接收响应,我们必须在调用
OnPong
之前保持ping_responder
对象的活动状态。毕竟,ping_responder
拥有其消息管道端点。如果它被销毁了,那么端点也被销毁了,将没有任何东西可以接收响应消息。
我们已经解决了将消息从 renderer 进程发送到浏览器进程的难题。我们只需要从上面获取 receiver
对象,然后以某种方式将其传递到浏览器进程,在浏览器进程中,可以将其转换为发送接收到的消息的 Receiver
。
发送 PendingReceiver
到浏览器
PendingReceiver
(以及一般的消息管道端点)只是另一种可以通过 mojom 消息自由发送的对象类型。获取 PendingReceiver
的最常见方法是将其作为方法参数传递到其他已连接的接口上。
在浏览器中,渲染器(renderer)的 RenderFrameImpl
与其对应的 RenderFrameHostImpl
之间始终连接的一个接口是 BrowserInterfaceBroker
。此接口是获取其他接口的工厂。它的 GetInterface
方法使用 GenericPendingReceiver
,它允许传递任意接口接收器(receiver)。
interface BrowserInterfaceBroker {
GetInterface(mojo_base.mojom.GenericPendingReceiver receiver);
}
由于 GenericPendingReceiver
可以从任何特定的 PendingReceiver
隐式构造,因此它可以使用它先前通过 BindNewPipeAndPassReceiver
创建的receiver
对象调用此方法:
RenderFrame* my_frame = GetMyFrame();
my_frame->GetBrowserInterfaceBroker().GetInterface(std::move(receiver));
这将把 PendingReceiver
端点传输到浏览器进程,相应的 BrowserInterfaceBroker
实现将在浏览器进程中接收到它。
实现接口
最后,我们在浏览器端实现 PingResponder
接口。
#include "example/public/mojom/ping_responder.mojom.h"
class PingResponderImpl : example::mojom::PingResponder {
public:
explicit PingResponderImpl(mojo::PendingReceiver receiver)
: receiver_(this, std::move(receiver)) {}
// example::mojom::PingResponder:
void Ping(PingCallback callback) override {
// Respond with a random 4, chosen by fair dice roll.
std::move(callback).Run(4);
}
private:
mojo::Receiver receiver_;
DISALLOW_COPY_AND_ASSIGN(PingResponderImpl);
};
RenderFrameHostImpl
拥有 BrowserInterfaceBroker
的实现。当此实现接收到 GetInterface
方法调用时,它将调用以前为此特定接口注册的处理程序。
// render_frame_host_impl.h
class RenderFrameHostImpl
...
void GetPingResponder(mojo::PendingReceiver receiver);
...
private:
...
std::unique_ptr ping_responder_;
...
};
// render_frame_host_impl.cc
void RenderFrameHostImpl::GetPingResponder(
mojo::PendingReceiver receiver) {
ping_responder_ = std::make_unique(std::move(receiver));
}
// browser_interface_binders.cc
void PopulateFrameBinders(RenderFrameHostImpl* host,
mojo::BinderMap* map) {
...
// Register the handler for PingResponder.
map->Add(base::BindRepeating(
&RenderFrameHostImpl::GetPingResponder, base::Unretained(host)));
}
此设置足以在渲染器帧与其浏览器端主机对象之间建立新的接口连接。
假设我们让 ping_responder
对象在呈现器(renderer)中存活足够长的时间,我们最终会看到它的 OnPong
回调被调用,其完全随机值为4,在上面的浏览器端实现所定义。
服务概述
上面只描述了 Mojo IPC 在 Chromium 中的应用。虽然 renderer-to-browser 的消息传递很简单,而且可能是最流行的用法,我们正在逐步地将代码库拆解为一组服务(service),比传统的 Content browser/renderer/gpu/utility process 拆分方式粒度稍大。
一个 service
是一个独立的代码库,它实现了一个或多个相关的特性或行为,并且与外部代码的交互是通过 Mojo 接口连接进行的,通常由浏览器进程代理。
每个服务定义并实现一个主 Mojo 接口,浏览器可以使用该接口来管理服务的实例。
示例
通常需要多个步骤才能启动新服务并在Chromium中运行:
- 定义主服务接口并实现
- 在进程外的代码中连接实现
- 编写一些浏览器逻辑以启动服务进程
定义服务
通常,服务定义在 services
目录中,在这个示例中,我们为 Chromium 定义一个新服务,定义在 chrome/services
目录中。
创建 mojom 文件:
// src/chrome/services/math/public/mojom/math_service.mojom
module math.mojom;
interface MathService {
Divide(int32 dividend, int32 divisor) => (int32 quotient);
};
# src/chrome/services/math/public/mojom/BUILD.gn
import("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
sources = [
"math_service.mojom",
]
}
MathService
的实现:
// src/chrome/services/math/math_service.h
#include "base/macros.h"
#include "chrome/services/math/public/mojom/math_service.mojom.h"
namespace math {
class MathService : public mojom::MathService {
public:
explicit MathService(mojo::PendingReceiver receiver);
~MathService() override;
private:
// mojom::MathService:
void Divide(int32_t dividend,
int32_t divisor,
DivideCallback callback) override;
mojo::Receiver receiver_;
DISALLOW_COPY_AND_ASSIGN(MathService);
};
} // namespace math
// src/chrome/services/math/math_service.cc
#include "chrome/services/math/math_service.h"
namespace math {
MathService::MathService(mojo::PendingReceiver receiver)
: receiver_(this, std::move(receiver)) {}
MathService::~MathService() = default;
void MathService::Divide(int32_t dividend,
int32_t divisor,
DivideCallback callback) {
// Respond with the quotient!
std::move(callback).Run(dividend / divisor);
}
} // namespace math
# src/chrome/services/math/BUILD.gn
source_set("math") {
sources = [
"math.cc",
"math.h",
]
deps = [
"//base",
"//chrome/services/math/public/mojom",
]
}
现在我们有了一个完全定义的 MathService
实现,可以在进程内或进程外提供。
连接服务实现
我们在 chrome/utility/services.cc
中注册一个工厂函数。
auto RunMathService(mojo::PendingReceiver receiver) {
return std::make_unique(std::move(receiver));
}
mojo::ServiceFactory* GetMainThreadServiceFactory() {
// Existing factories...
static base::NoDestructor factory {
RunFilePatcher,
RunUnzipper,
// We add our own factory to this list
RunMathService,
//...
完成此操作后,浏览器进程现在可以启动 MathService
的新进程外实例。
启动服务
如果你在进程中运行服务,就没有什么有趣的事情要做了。你可以像其他任何对象一样实例化服务实现,但你也可以通过 Mojo Remote
程序与它进行对话,就好像它已经脱离流程一样。
在进程外启动上面的服务实例,需要使用 ServiceProcessHost
API:
mojo::Remote math_service =
content::ServiceProcessHost::Launch(
content::ServiceProcessHost::LaunchOptions()
.WithSandboxType(content::SandboxType::kUtility)
.WithDisplayName("Math!")
.Pass());
除非崩溃,否则启动的进程将与 math_service
一直存活,可以通过销毁(或重置)math_service
也会强制拆除进程。
我们现在可以执行进程外分配:
// NOTE: As a client, we do not have to wait for any acknowledgement or
// confirmation of a connection. We can start queueing messages immediately and
// they will be delivered as soon as the service is up and running.
math_service->Divide(
42, 6, base::BindOnce([](int32_t quotient) { LOG(INFO) << quotient; }));
注意:为了确保响应回调的执行,
mojo::Remote
对象必须保存存活。