概述
Mojo C++ 绑定 API 利用C++ 系统 API提供一组更自然的原语,用于通过 Mojo 消息管道进行通信。结合从++Mojom IDL 和绑定生成器++生成的代码,用户可以轻松地跨任意进程内和进程间边界连接接口客户端和实现。
本文档通过示例代码片段提供了绑定 API 用法的详细指南。有关详细的 API 参考,请参阅//mojo/public/cpp/bindings中的标头。
有关针对 Chromium 开发人员的简化指南,请参阅此链接。
入门
When a Mojom IDL file is processed by the bindings generator, C++ code is emitted in a series of .h
and .cc
files with names based on the input .mojom
file. Suppose we create the following Mojom file at //services/db/public/mojom/db.mojom
:
module db.mojom;
interface Table {
AddRow(int32 key, string data);
};
interface Database {
CreateTable(Table& table);
};
And a GN target to generate the bindings in //services/db/public/mojom/BUILD.gn
:
import("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
sources = [
"db.mojom",
]
}
Ensure that any target that needs this interface depends on it, e.g. with a line like:
deps += [ '//services/db/public/mojom' ]
If we then build this target:
ninja -C out/r services/db/public/mojom
This will produce several generated source files, some of which are relevant to C++ bindings. Two of these files are:
out/gen/services/db/public/mojom/db.mojom.cc out/gen/services/db/public/mojom/db.mojom.h
You can include the above generated header in your sources in order to use the definitions therein:
#include "services/business/public/mojom/factory.mojom.h"
class TableImpl : public db::mojom::Table {
// ...
};
本文档涵盖了 Mojom IDL 为 C++ 消费者生成的不同类型的定义,以及如何有效地使用它们来跨消息管道进行通信。
Interfaces
Mojom IDL 接口在生成的标头中被转换为相应的 C++(纯虚拟)类接口定义,包括为接口上的每个请求消息生成的单个方法签名。在内部还有用于消息序列化和反序列化的生成代码,但是这个细节对绑定消费者是隐藏的。
基本使用
让我们考虑一个新//sample/logger.mojom
的定义一个简单的日志记录接口,客户端可以使用它来记录简单的字符串消息:
module sample.mojom;
interface Logger {
Log(string message);
};
通过绑定生成器运行它会生成logger.mojom.h
,其中忽略了其他细节:
namespace sample {
namespace mojom {
class Logger {
virtual ~Logger() {}
virtual void Log(const std::string& message) = 0;
};
} // namespace mojom
} // namespace sample
Remote and PendingReceiver
APendingReceiver<T>
本质上只是一个类型化的容器,用于容纳 aRemote<T>
的管道的另一端------接收端------直到它可以被路由到某个将绑定 它的实现。除了保持管道端点并携带有用的编译时类型信息外,它PendingReceiver<T>
实际上没有做任何事情。
![](https://i-blog.csdnimg.cn/blog_migrate/a2e2cc5fc9be4f842cce6358f37fcb60.png)
创建接口管道
一种方法是手动创建管道并用强类型对象包裹每一端:
#include "sample/logger.mojom.h"
mojo::MessagePipe pipe;
mojo::Remote<sample::mojom::Logger> logger(
mojo::PendingRemote<sample::mojom::Logger>(std::move(pipe.handle0), 0));
mojo::PendingReceiver<sample::mojom::Logger> receiver(std::move(pipe.handle1));
这相当冗长,但 C++ 绑定库提供了一种更方便的方法来完成同样的事情。remote.h定义了一个BindNewPipeAndPassReceiver
方法:
mojo::Remote<sample::mojom::Logger> logger;
auto receiver = logger.BindNewPipeAndPassReceiver();
第二个片段等同于第一个片段。
注意: 在上面的第一个示例中,您可能会注意到mojo::PendingRemote<Logger>
. 这类似PendingReceiver<T>
,因为它仅持有管道句柄,实际上不能在管道上读取或写入消息。这种类型 和 都PendingReceiver<T>
可以安全地从一个序列自由地移动到另一个序列,而绑定Remote<T>
绑定到单个序列。
ARemote<T>
可以通过调用它的Unbind()
方法来解除绑定,该方法返回一个新的PendingRemote<T>
. 相反,anRemote<T>
可以绑定(并因此获得所有权)an PendingRemote<T>
,以便可以在管道上进行接口调用。
的序列绑定特性Remote<T>
对于支持其消息响应和连接错误通知的安全分发是必要的。
一旦 被PendingRemote<Logger>
绑定,我们就可以立即开始Logger
对其调用接口方法,这将立即将消息写入管道。这些消息将在管道的接收端排队,直到有人绑定到它并开始阅读它们。
logger->Log("Hello!");
这实际上Log
向管道写入了一条消息。
![](https://i-blog.csdnimg.cn/blog_migrate/5344ab53ee3587f60f40aae693010aa0.png)
但正如上面提到的,PendingReceiver
实际上并没有做任何事情 ,因此该消息将永远停留在管道上。我们需要一种方法来从管道的另一端读取消息并发送它们。我们必须绑定挂起的接收者。
Binding a Pending Receiver 绑定一个挂起的接收者
绑定库中有许多不同的帮助器类用于绑定消息管道的接收端。其中最原始的是mojo::Receiver<T>
。mojo::Receiver<T>
将 的实现T
与单个绑定消息管道端点(通过 a mojo::PendingReceiver<T>
)桥接起来,它会持续监视其可读性。
只要绑定管道变得可读,就会Receiver
安排一个任务来读取、反序列化(使用生成的代码)并将所有可用消息分派给绑定实现T
。下面是接口的示例实现Logger
。请注意,实现本身拥有一个mojo::Receiver
. 这是一个常见的模式,因为绑定的实现必须比任何mojo::Receiver
绑定它的实现都长寿。
#include "base/logging.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 a PendingReceiver in the constructor.
explicit LoggerImpl(mojo::PendingReceiver<sample::mojom::Logger> receiver)
: receiver_(this, std::move(receiver)) {}
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
~Logger() override {}
// sample::mojom::Logger:
void Log(const std::string& message) override {
LOG(ERROR) << "[Logger] " << message;
}
private:
mojo::Receiver<sample::mojom::Logger> receiver_;
};
LoggerImpl
现在我们可以在我们的 上构造一个PendingReceiver<Logger>
,之前排队的Log
消息将按 的LoggerImpl
顺序尽快发送:
LoggerImpl impl(std::move(receiver));
The diagram below illustrates the following sequence of events, all set in motion by the above line of code:
下图说明了以下事件序列,所有事件都由上面的代码行启动:
-
LoggerImpl
调用构造函数,将传递PendingReceiver<Logger>
给Receiver
。 -
Receiver
获取 的管道端点的所有权并PendingReceiver<Logger>
开始观察它的可读性。Log
管道是立即可读的,因此安排了一个任务来尽快从管道中读取待处理的消息。 -
消息
Log
被读取和反序列化,导致在其绑定上Receiver
调用实现。Logger::LogLoggerImpl
![](https://i-blog.csdnimg.cn/blog_migrate/1b48f0af1cd68c71d95b76132b851843.png)
因此,我们的实现最终将"Hello!"
通过 记录客户端的消息LOG(ERROR)
。
注意: 只要绑定它的对象(即 mojo::Receiver
上面示例中的 )保持活动状态,消息就会从管道中读取和发送。
Receiving Responses
一些 Mojom 接口方法需要一个响应。假设我们修改我们的Logger
界面,以便可以像这样查询最后记录的行
module sample.mojom;
interface Logger {
Log(string message);
GetTail() => (string message);
};
The generated C++ interface will now look like:
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
:实现使用callback
参数来响应 请求,而客户端传递callback
参数以异步方式receive
响应。GetTailCallback
传递给实现的参数GetTail
是序列仿射的。GetTail
它必须在调用的同一序列上调用。客户端callback
在它们调用的相同序列上运行GetTail
(它们绑定到的序列logger
)。这是一个更新的实现
class LoggerImpl : public sample::mojom::Logger {
public:
// NOTE: A common pattern for interface implementations which have one
// instance per client is to take a PendingReceiver in the constructor.
explicit LoggerImpl(mojo::PendingReceiver<sample::mojom::Logger> receiver)
: receiver_(this, std::move(receiver)) {}
~Logger() override {}
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
// 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::Receiver<sample::mojom::Logger> receiver_;
std::vector<std::string> lines_;
};
以及更新的客户端调用:
void OnGetTail(const std::string& message) {
LOG(ERROR) << "Tail was: " << message;
}
logger->GetTail(base::BindOnce(&OnGetTail));
在幕后,实现端回调实际上是序列化响应参数并将它们写入管道以传递回客户端。同时,客户端回调由一些内部逻辑调用,该逻辑监视管道中是否有传入的响应消息,一旦消息到达就对其进行读取和反序列化,然后使用反序列化的参数调用回调。
连接错误
如果管道断开连接,两个端点都将能够观察到连接错误(除非断开连接是由关闭/销毁端点引起的,在这种情况下,端点将不会收到此类通知)。如果在断开连接时端点有剩余的传入消息,则在消息耗尽之前不会触发连接错误。
管道断开可能由以下原因引起:
-
Mojo 系统级原因:进程终止、资源耗尽等。
-
由于在处理接收到的消息时出现验证错误,绑定会关闭管道。
-
对等端点已关闭。例如,远程端是一个绑定
mojo::Remote<T>
,它被销毁了。
无论根本原因如何,当在接收方端点上遇到连接错误时,将调用该端点的断开连接处理程序(如果已设置)。 这个处理程序很简单,只要端点绑定到同一个管道就base::OnceClosure
只能调用*一次。*通常,客户端和实现使用此处理程序进行某种清理,或者------特别是如果错误是意外的------创建一个新管道并尝试与其建立新连接。
所有消息管道绑定 C++ 对象(例如 , mojo::Receiver<T>
,等 )都支持通过方法设置它们的断开连接处理mojo::Remote<T>
程序。set_disconnect_handler
我们可以设置另一个端到端Logger
示例来演示断开连接处理程序调用。假设LoggerImpl
在其构造函数中设置了以下断开连接处理程序:
LoggerImpl::LoggerImpl(mojo::PendingReceiver<sample::mojom::Logger> receiver)
: receiver_(this, std::move(receiver)) {
receiver_.set_disconnect_handler(
base::BindOnce(&LoggerImpl::OnError, base::Unretained(this)));
}
void LoggerImpl::OnError() {
LOG(ERROR) << "Client disconnected! Purging log lines.";
lines_.clear();
}
mojo::Remote<sample::mojom::Logger> logger;
LoggerImpl impl(logger.BindNewPipeAndPassReceiver());
logger->Log("OK cool");
logger.reset(); // Closes the client end.
只要impl
在这里保持活动状态,它最终就会收到Log
消息,然后立即调用绑定的回调,输出"Client disconnected! Purging log lines."
. 与所有其他接收器回调一样,一旦相应的接收器对象被销毁,断开连接处理程序将永远不会被调用。
使用base::Unretained
是安全的 ,因为错误处理程序永远不会在receiver_
和this
拥有 的生命周期之后被调用receiver_
。
关于端点生命周期和回调的说明
一旦 mojo::Remote<T>
被销毁,就可以保证挂起的回调以及连接错误处理程序(如果已注册)不会被调用。
一旦 mojo::Receiver<T>
被销毁,就可以保证不再将方法调用分派给实现,并且不会调用连接错误处理程序(如果已注册)。
处理进程崩溃和回调的最佳实践
当调用带有回调的 mojo 接口方法时,一个常见的情况是调用者想知道另一个端点是否被拆除(例如,因为崩溃)。在这种情况下,消费者通常想知道响应回调是否不会运行。这个问题有不同的解决方案,具体取决于Remote<T>
举行的方式:
-
消费者拥有
Remote<T>
:set_disconnect_handler
应该被使用。 -
消费者不拥有
Remote<T>
:根据调用者想要的行为,有两个助手。如果调用者想要确保错误处理程序运行,那么**mojo::WrapCallbackWithDropHandler** 应该使用。如果调用者希望回调始终运行,则应**mojo::WrapCallbackWithDefaultInvokeIfNotRun** 使用 helper。对于这两个助手,应该遵循通常的回调注意以确保回调不会在消费者被销毁后运行(例如,因为消费者的所有者比Remote<T>
消费者长寿)。这包括使用**base::WeakPtr** 或**base::RefCounted**。还应该注意的是,使用这些助手,回调可以在 Remote 被重置或销毁时同步运行。
关于Ordering的注意事项
如前一节所述,关闭管道的一端最终会触发另一端的连接错误。然而,重要的是要注意这个事件本身是相对于管道上的任何其他事件(例如写消息)排序的。
这意味着写一些人为的东西是安全的:
LoggerImpl::LoggerImpl(mojo::PendingReceiver<sample::mojom::Logger> receiver,
base::OnceClosure disconnect_handler)
: receiver_(this, std::move(receiver)) {
receiver_.set_disconnect_handler(std::move(disconnect_handler));
}
void GoBindALogger(mojo::PendingReceiver<sample::mojom::Logger> receiver) {
base::RunLoop loop;
LoggerImpl impl(std::move(receiver), loop.QuitClosure());
loop.Run();
}
void LogSomething() {
mojo::Remote<sample::mojom::Logger> logger;
bg_thread->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&GoBindALogger, logger.BindNewPipeAndPassReceiver()));
logger->Log("OK Computer");
}
当logger
超出范围时,它会立即关闭消息管道的末端,但实现端在收到发送的消息之前不会注意到这一点Log
。因此,impl
上面将首先记录我们的消息,然后看到连接错误并退出运行循环。
类型
Enums
++Mojom 枚举++ 直接转换为等效的强类型 C++11 枚举类作为int32_t
基础类型。Mojom 和 C++ 的类型名和值名是相同的。Mojo 还总是定义一个特殊的枚举器kMaxValue
,它共享最高枚举器的值:这使得在直方图中记录 Mojo 枚举并与遗留 IPC 互操作变得容易。
例如,考虑以下 Mojom 定义:
module business.mojom;
enum Department {
kEngineering,
kMarketing,
kSales,
};
This translates to the following C++ definition:
namespace business {
namespace mojom {
enum class Department : int32_t {
kEngineering,
kMarketing,
kSales,
kMaxValue = kSales,
};
} // namespace mojom
} // namespace business
Structs
++Mojom 结构++可用于将字段的逻辑分组定义为新的复合类型。每个 Mojom 结构都会生成一个同名的、有代表性的 C++ 类,具有相应 C++ 类型的同名公共字段,以及几个有用的公共方法。
例如,考虑以下 Mojom 结构:
module business.mojom;
struct Employee {
int64 id;
string username;
Department department;
};
This would generate a C++ class like so:
namespace business {
namespace mojom {
class Employee;
using EmployeePtr = mojo::StructPtr<Employee>;
class Employee {
public:
// Default constructor - applies default values, potentially ones specified
// explicitly within the Mojom.
Employee();
// Value constructor - an explicit argument for every field in the struct, in
// lexical Mojom definition order.
Employee(int64_t id, const std::string& username, Department department);
// Creates a new copy of this struct value
EmployeePtr Clone();
// Tests for equality with another struct value of the same type.
bool Equals(const Employee& other);
// Equivalent public fields with names identical to the Mojom.
int64_t id;
std::string username;
Department department;
};
} // namespace mojom
} // namespace business
请注意,当用作消息参数或另一个 Mojom 结构中的字段时,类型struct
由 move-only 助手包装mojo::StructPtr
,这大致等同于带有std::unique_ptr
一些附加实用方法的 。这允许结构值可以为空,并且结构类型可以是潜在的自引用。
每个生成的结构类都有一个静态New()
方法,该方法返回一个新的mojo::StructPtr<T>
包装类的新实例,该实例是通过转发来自New
. 例如:
mojom::EmployeePtr e1 = mojom::Employee::New();
e1->id = 42;
e1->username = "mojo";
e1->department = mojom::Department::kEngineering;
相当于
auto e1 = mojom::Employee::New(42, "mojo", mojom::Department::kEngineering);
现在,如果我们定义一个接口,如
interface EmployeeManager {
AddEmployee(Employee e);
};
We'll get this C++ interface to implement:
class EmployeeManager {
public:
virtual ~EmployeManager() {}
virtual void AddEmployee(EmployeePtr e) = 0;
};
我们可以从 C++ 代码发送此消息,如下所示:
mojom::EmployeManagerPtr manager = ...;
manager->AddEmployee(
Employee::New(42, "mojo", mojom::Department::kEngineering));
// or
auto e = Employee::New(42, "mojo", mojom::Department::kEngineering);
manager->AddEmployee(std::move(e));
Unions
与structs类似,标记的联合生成一个同名的、有代表性的 C++ 类,它通常包装在一个mojo::StructPtr<T>
.
与结构不同,所有生成的联合字段都是私有的,必须使用访问器进行检索和操作。字段foo
可由 访问get_foo()
和设置set_foo()
。每个字段还有一个布尔值is_foo()
,指示联合当前是否正在采用 field 的值foo
以排除所有其他联合字段。
最后,每个生成的联合类还有一个嵌套的Tag
枚举类,它枚举所有命名的联合字段。Mojom 联合值的当前类型可以通过调用which()
返回 a 的方法来确定Tag
。
例如,考虑以下 Mojom 定义:
union Value {
int64 int_value;
float float_value;
string string_value;
};
interface Dictionary {
AddValue(string key, Value value);
};
This generates the following C++ interface:
class Value {
public:
~Value() {}
};
class Dictionary {
public:
virtual ~Dictionary() {}
virtual void AddValue(const std::string& key, ValuePtr value) = 0;
};
我们可以这样使用它:
ValuePtr value = Value::NewIntValue(42);
CHECK(value->is_int_value());
CHECK_EQ(value->which(), Value::Tag::kIntValue);
value->set_float_value(42);
CHECK(value->is_float_value());
CHECK_EQ(value->which(), Value::Tag::kFloatValue);
value->set_string_value("bananas");
CHECK(value->is_string_value());
CHECK_EQ(value->which(), Value::Tag::kStringValue);
最后,请注意,如果联合值当前未被给定字段占用,则访问该字段的尝试将 DCHECK:
ValuePtr value = Value::NewIntValue(42);
LOG(INFO) << "Value is " << value->string_value(); // DCHECK!
Sending Interfaces Over Interfaces
我们知道如何创建接口管道并以一些有趣的方式使用它们的 Remote 和 PendingReceiver 端点。这仍然不是有趣的 IPC!Mojo IPC 的主要功能是能够跨其他接口传输接口端点,所以让我们来看看如何实现这一点。
Sending mojo::PendingReceiver
考虑一个新的例子 Mojom in //sample/db.mojom
:
module db.mojom;
interface Table {
void AddRow(int32 key, string data);
};
interface Database {
AddTable(pending_receiver<Table> table);
};
如Mojom IDL 文档中所述,pending_receiver<Table>
语法与上述部分中讨论的类型完全对应PendingReceiver<T>
,实际上为这些接口生成的代码大致如下:
namespace db {
namespace mojom {
class Table {
public:
virtual ~Table() {}
virtual void AddRow(int32_t key, const std::string& data) = 0;
}
class Database {
public:
virtual ~Database() {}
virtual void AddTable(mojo::PendingReceiver<Table> table);
};
} // namespace mojom
} // namespace db
We can put this all together now with an implementation of Table
and Database
:
#include "sample/db.mojom.h"
class TableImpl : public db::mojom:Table {
public:
explicit TableImpl(mojo::PendingReceiver<db::mojom::Table> receiver)
: receiver_(this, std::move(receiver)) {}
~TableImpl() override {}
// db::mojom::Table:
void AddRow(int32_t key, const std::string& data) override {
rows_.insert({key, data});
}
private:
mojo::Receiver<db::mojom::Table> receiver_;
std::map<int32_t, std::string> rows_;
};
class DatabaseImpl : public db::mojom::Database {
public:
explicit DatabaseImpl(mojo::PendingReceiver<db::mojom::Database> receiver)
: receiver_(this, std::move(receiver)) {}
~DatabaseImpl() override {}
// db::mojom::Database:
void AddTable(mojo::PendingReceiver<db::mojom::Table> table) {
tables_.emplace_back(std::make_unique<TableImpl>(std::move(table)));
}
private:
mojo::Receiver<db::mojom::Database> receiver_;
std::vector<std::unique_ptr<TableImpl>> tables_;
};
让我们看看如何使用它。
mojo::Remote<db::mojom::Database> database;
DatabaseImpl db_impl(database.BindNewPipeAndPassReceiver());
mojo::Remote<db::mojom::Table> table1, table2;
database->AddTable(table1.BindNewPipeAndPassReceiver());
database->AddTable(table2.BindNewPipeAndPassReceiver());
table1->AddRow(1, "hiiiiiiii");
table2->AddRow(2, "heyyyyyy");
请注意,我们可以再次立即开始使用新Table
管道,即使它们的mojo::PendingReceiver<db::mojom::Table>
端点仍在传输中。
Sending mojo::PendingRemote
当然我们也可以发送Remote
s
interface TableListener {
OnRowAdded(int32 key, string data);
};
interface Table {
AddRow(int32 key, string data);
AddListener(pending_remote<TableListener> listener);
};
这将生成Table::AddListener
如下签名:
virtual void AddListener(mojo::PendingRemote<TableListener> listener) = 0;
这可以像这样使用:
mojo::PendingRemote<db::mojom::TableListener> listener;
TableListenerImpl impl(listener.InitWithNewPipeAndPassReceiver());
table->AddListener(std::move(listener));
其他接口绑定类型
上面的接口部分涵盖了最常见绑定对象类型的基本用法:Remote
、PendingReceiver
和Receiver
。虽然这些类型可能是实践中最常用的类型,但还有其他几种绑定客户端和实现端接口管道的方法。
Self-owned Receivers
self-owned receiver 作为独立对象存在,它拥有自己的接口实现,并在其绑定的接口端点检测到错误时自动清理自身。该**MakeSelfOwnedReceiver**函数用于创建这样的接收器。.
class LoggerImpl : public sample::mojom::Logger {
public:
LoggerImpl() {}
~LoggerImpl() override {}
// sample::mojom::Logger:
void Log(const std::string& message) override {
LOG(ERROR) << "[Logger] " << message;
}
private:
// NOTE: This doesn't own any Receiver object!
};
mojo::Remote<db::mojom::Logger> logger;
mojo::MakeSelfOwnedReceiver(std::make_unique<LoggerImpl>(),
logger.BindNewPipeAndPassReceiver());
logger->Log("NOM NOM NOM MESSAGES");
现在,只要logger
系统中某处保持打开状态,LoggerImpl
另一端的绑定就会保持活动状态。
Receiver Sets
有时与多个客户端共享单个实现实例很有用。**ReceiverSet**使这变得容易。考虑一下 Mojom:
module system.mojom;
interface Logger {
Log(string message);
};
interface LoggerProvider {
GetLogger(Logger& logger);
};
有时与多个 客户端 共享单个实现实例很有用 。**ReceiverSet**使这变得容易。考虑一下 Mojom
class LogManager : public system::mojom::LoggerProvider,
public system::mojom::Logger {
public:
explicit LogManager(mojo::PendingReceiver<system::mojom::LoggerProvider> receiver)
: provider_receiver_(this, std::move(receiver)) {}
~LogManager() {}
// system::mojom::LoggerProvider:
void GetLogger(mojo::PendingReceiver<Logger> receiver) override {
logger_receivers_.Add(this, std::move(receiver));
}
// system::mojom::Logger:
void Log(const std::string& message) override {
LOG(ERROR) << "[Logger] " << message;
}
private:
mojo::Receiver<system::mojom::LoggerProvider> provider_receiver_;
mojo::ReceiverSet<system::mojom::Logger> logger_receivers_;
};
Remote Sets
与ReceiverSet
上面类似,有时维护一组Remote
s 是有用的,例如 一 组观察某个事件的 客户端 。**RemoteSet**在这里提供帮助。以 Mojom 为例:
module db.mojom;
interface TableListener {
OnRowAdded(int32 key, string data);
};
interface Table {
AddRow(int32 key, string data);
AddListener(pending_remote<TableListener> listener);
};
An implementation of Table might look something like like this:
class TableImpl : public db::mojom::Table {
public:
TableImpl() {}
~TableImpl() override {}
// db::mojom::Table:
void AddRow(int32_t key, const std::string& data) override {
rows_.insert({key, data});
for (auto& listener : listeners_) {
listener->OnRowAdded(key, data);
}
}
void AddListener(mojo::PendingRemote<db::mojom::TableListener> listener) {
listeners_.Add(std::move(listener));
}
private:
mojo::RemoteSet<db::mojom::Table> listeners_;
std::map<int32_t, std::string> rows_;
};
Associated Interfaces 关联接口
关联接口是以下接口:
-
允许在单个消息管道上运行多个接口,同时保留消息顺序。
-
使接收者可以从多个序列访问单个消息管道。
为远程/接收方字段引入了新类型pending_associated_remote
和。pending_associated_receiver
例如:
interface Bar {};
struct Qux {
pending_associated_remote<Bar> bar;
};
interface Foo {
// Uses associated remote.
PassBarRemote(pending_associated_remote<Bar> bar);
// Uses associated receiver.
PassBarReceiver(pending_associated_receiver<Bar> bar);
// Passes a struct with associated interface pointer.
PassQux(Qux qux);
// Uses associated interface pointer in callback.
AsyncGetBar() => (pending_associated_remote<Bar> bar);
};
在每种情况下,接口 impl/client 将使用相同的消息管道进行通信,相关的 remote/receiver 通过该消息管道传递。
Using associated interfaces in C++
生成 C++ 绑定时,pending_associated_remoteBar
被映射到mojo::PendingAssociatedRemote<Bar>
;pending_associated_receiver 到mojo::PendingAssociatedReceiver<Bar>
.
// In mojom:
interface Foo {
...
PassBarRemote(pending_associated_remote<Bar> bar);
PassBarReceiver(pending_associated_receiver<Bar> bar);
...
};
// In C++:
class Foo {
...
virtual void PassBarRemote(mojo::PendingAssociatedRemote<Bar> bar) = 0;
virtual void PassBarReceiver(mojo::PendingAssociatedReceiver<Bar> bar) = 0;
...
};
Passing pending associated receivers 传递挂起的关联接收器
假设您已经有一个Remote<Foo> foo
,并且您想调用PassBarReceiver()
它。你可以做:
mojo::PendingAssociatedRemote<Bar> pending_bar;
mojo::PendingAssociatedReceiver<Bar> bar_receiver = pending_bar.InitWithNewEndpointAndPassReceiver();
foo->PassBarReceiver(std::move(bar_receiver));
mojo::AssociatedRemote<Bar> bar;
bar.Bind(std::move(pending_bar));
bar->DoSomething();
首先,代码创建了一个类型为 的关联接口Bar
。它看起来与您设置非关联接口的操作非常相似。一个重要的区别是两个关联端点之一(要么bar_receiver
要么pending_bar
)必须通过另一个接口发送。这就是接口与现有消息管道相关联的方式。
需要注意的是不能bar->DoSomething()
在pass前调用bar_receiver
。这是 FIFO 保证所要求的:在接收方,当DoSomething
调用的消息到达时,我们希望AssociatedReceiver<Bar>
在处理任何后续消息之前将其分派给相应的消息。如果bar_receiver
在后续消息中,则消息调度将陷入死锁。另一方面,一经bar_receiver
发送bar
就可以使用。无需等到bar_receiver
绑定到远程端的实现。
AssociatedRemote
提供了一种BindNewEndpointAndPassReceiver
使代码更短的方法。下面的代码达到了同样的目的:
mojo::AssociatedRemote<Bar> bar;
foo->PassBarReceiver(bar.BindNewEndpointAndPassReceiver());
bar->DoSomething();
The implementation of Foo looks like this:
class FooImpl : public Foo {
...
void PassBarReceiver(mojo::AssociatedReceiver<Bar> bar) override {
bar_receiver_.Bind(std::move(bar));
...
}
...
Receiver<Foo> foo_receiver_;
AssociatedReceiver<Bar> bar_receiver_;
};
在此示例中,bar_receiver_
的生命周期与 的生命周期相关联FooImpl
。但你不必那样做。例如,您可以传递bar2
给另一个序列以绑定到AssociatedReceiver<Bar>
那里。
当底层消息管道断开连接(例如,foo
或被foo_receiver_
销毁)时,所有关联的接口端点(例如,bar
和bar_receiver_
)将收到断开连接错误。
Passing associated remotes
同样,假设您已经有一个Remote<Foo> foo
,并且您想调用PassBarRemote()
它。你可以做:
mojo::AssociatedReceiver<Bar> bar_receiver(some_bar_impl);
mojo::PendingAssociatedRemote<Bar> bar;
mojo::PendingAssociatedReceiver<Bar> bar_pending_receiver = bar.InitWithNewEndpointAndPassReceiver();
foo->PassBarRemote(std::move(bar));
bar_receiver.Bind(std::move(bar_pending_receiver));
下面的代码达到了同样的目的:
mojo::AssociatedReceiver<Bar> bar_receiver(some_bar_impl);
mojo::PendingAssociatedRemote<Bar> bar;
bar_receiver.Bind(bar.InitWithNewPipeAndPassReceiver());
foo->PassBarRemote(std::move(bar));
性能考虑
在与主序列(主接口所在的位置)不同的序列上使用关联接口时:
-
发送消息:发送直接发生在调用序列上。所以没有序列跳跃。
-
接收消息:绑定在与主接口不同的序列上的关联接口在调度期间会产生额外的序列跳跃。
因此,性能方面的关联接口更适合在主序列上接收消息的场景。
Testing
关联接口需要与主接口关联才能使用。这意味着关联接口的一端必须通过主接口的一端发送,或者通过本身已经具有主接口的另一关联接口的一端发送。
如果您想在不首先关联它的情况下测试关联的接口端点,您可以使用AssociatedRemote::BindNewEndpointAndPassDedicatedReceiver
. 这将创建有效的关联接口端点,这些端点实际上并不与其他任何东西关联。
阅读更多
同步调用
在决定使用同步呼叫之前仔细考虑
尽管同步调用很方便,但只要不是绝对必要,就应该避免使用它们:
-
同步调用损害了并行性,因此损害了性能。
-
重新进入会改变消息顺序并产生您在编码时可能从未想过的调用堆栈。这一直是一个巨大的痛苦。
-
同步调用可能会导致死锁
Mojom changes
A new attribute [Sync]
(or [Sync=true]
) is introduced for methods. For example:
interface Foo {
[Sync]
SomeSyncCall() => (Bar result);
};
它表示当SomeSyncCall()
被调用时,调用线程的控制流被阻塞,直到收到响应。
不允许将此属性用于没有响应的函数。如果你只需要等到服务端处理完调用,你可以使用一个空的响应参数列表:
[Sync]
SomeSyncCallWithNoResult() => ();
Generated bindings (C++)
The generated C++ interface of the Foo interface above is:
class Foo {
public:
// The service side implements this signature. The client side can
// also use this signature if it wants to call the method asynchronously.
virtual void SomeSyncCall(SomeSyncCallCallback callback) = 0;
// The client side uses this signature to call the method synchronously.
virtual bool SomeSyncCall(BarPtr* result);
};
如您所见,客户端和服务端使用不同的签名。在客户端,response被映射为输出参数,boolean返回值表示操作是否成功。(返回 false 通常意味着发生了连接错误。)
在服务端,使用带有回调的签名。原因是在某些情况下,实现可能需要做一些同步方法结果所依赖的异步工作。
**注意:**您还可以在客户端使用带有回调的签名来异步调用该方法。
重入
在等待同步方法调用的响应时,调用线程会发生什么?它继续处理传入的同步请求消息(即同步方法调用);阻止其他消息,包括与正在进行的同步调用不匹配的异步消息和同步响应消息。
![](https://i-blog.csdnimg.cn/blog_migrate/167d3d187038ca2d23ebb67747f7938b.png)
请注意,与正在进行的同步呼叫不匹配的同步响应消息无法重新输入。那是因为它们对应于调用堆栈中向下的同步调用。因此,它们需要在堆栈展开时排队和处理。
避免死锁
请注意,重入行为不会阻止涉及异步调用的死锁。您需要避免调用序列,例如:
![](https://i-blog.csdnimg.cn/blog_migrate/17e61d4abdd450fbe8d7da7b54ec9c8d.png)
Read more
类型映射
在许多情况下,您可能更喜欢生成的 C++ 绑定使用更自然的类型来表示接口方法中的某些 Mojom 类型。例如,考虑一个 Mojom 结构,如下所示Rect
:
module gfx.mojom;
struct Rect {
int32 x;
int32 y;
int32 width;
int32 height;
};
interface Canvas {
void FillRect(Rect rect);
};
The Canvas Mojom interface would normally generate a C++ interface like:
class Canvas {
public:
virtual void FillRect(RectPtr rect) = 0;
};
然而,Chromium 树已经定义了一个 native gfx::Rect,它在含义上是等价的,但也有有用的辅助方法。与其在每个消息边界手动转换 agfx::Rect
和 Mojom 生成的对象RectPtr
,不如让 Mojom 绑定生成器生成:
class Canvas {
public:
virtual void FillRect(const gfx::Rect& rect) = 0;
}
定义 StructTraits
为了教导生成的绑定代码如何将任意本机类型序列T
化为任意 Mojom 类型mojom::U
,我们需要定义mojo::StructTraits模板的适当特化。有效特化StructTraits
必须定义以下静态方法:
-
Mojom 结构的每个字段的单个静态访问器,与结构字段具有完全相同的名称。这些访问器都必须采用(最好是 const)引用到本机类型的对象,并且必须返回与 Mojom 结构字段的类型兼容的值。这用于在消息序列化期间安全且一致地从本机类型中提取数据,而不会产生额外的复制成本。
-
一个单一的静态
Read
方法,它在给定 Mojom 结构的序列化表示的情况下初始化本机类型的实例。该Read
方法必须返回一个bool
以指示传入数据是被接受 (true
) 还是被拒绝 (false
)。
为了定义 的映射gfx::Rect
,我们需要以下StructTraits
特化,我们将在 中定义//ui/gfx/geometry/mojo/geometry_mojom_traits.h
:
#include "mojo/public/cpp/bindings/mojom_traits.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/mojo/geometry.mojom.h"
namespace mojo {
template <>
class StructTraits<gfx::mojom::RectDataView, gfx::Rect> {
public:
static int32_t x(const gfx::Rect& r) { return r.x(); }
static int32_t y(const gfx::Rect& r) { return r.y(); }
static int32_t width(const gfx::Rect& r) { return r.width(); }
static int32_t height(const gfx::Rect& r) { return r.height(); }
static bool Read(gfx::mojom::RectDataView data, gfx::Rect* out_rect);
};
} // namespace mojo
And in //ui/gfx/geometry/mojo/geometry_mojom_traits.cc:
#include "ui/gfx/geometry/mojo/geometry_mojom_traits.h"
namespace mojo {
// static
bool StructTraits<gfx::mojom::RectDataView, gfx::Rect>::Read(
gfx::mojom::RectDataView data,
gfx::Rect* out_rect) {
if (data.width() < 0 || data.height() < 0)
return false;
out_rect->SetRect(data.x(), data.y(), data.width(), data.height());
return true;
};
} // namespace mojo
请注意,如果传入或字段为负数,则该Read()
方法返回。这在反序列化期间充当验证步骤:如果客户端发送负宽度或负高度的 ,其消息将被拒绝并且管道将被关闭。这样,除了使调用站点和接口实现更加方便之外,类型映射还可以用于启用自定义验证逻辑。falsewidthheightgfx::Rect
当结构字段具有非原始类型(例如字符串或数组)时,建议在访问器中返回数据的只读视图以避免复制。它是安全的,因为输入对象保证比访问器方法返回的结果的使用时间更长。
以下示例用于StringPiece
返回 GURL 数据 ( //url/mojom/url_gurl_mojom_traits.h
) 的视图
The following example uses StringPiece
to return a view of the GURL's data (//url/mojom/url_gurl_mojom_traits.h
):
#include "base/strings/string_piece.h"
#include "url/gurl.h"
#include "url/mojom/url.mojom.h"
#include "url/url_constants.h"
namespace mojo {
template <>
struct StructTraits<url::mojom::UrlDataView, GURL> {
static base::StringPiece url(const GURL& r) {
if (r.possibly_invalid_spec().length() > url::kMaxURLChars ||
!r.is_valid()) {
return base::StringPiece();
}
return base::StringPiece(r.possibly_invalid_spec().c_str(),
r.possibly_invalid_spec().length());
}
} // namespace mojo
启用新类型映射
我们已经定义了StructTraits
必要的,但我们仍然需要向绑定生成器(以及构建系统)传授有关映射的信息。为此,我们必须向mojom
GN 中的目标添加更多信息:
# Without a typemap
mojom("mojom") {
sources = [
"rect.mojom",
]
}
# With a typemap.
mojom("mojom") {
sources = [
"rect.mojom",
]
cpp_typemaps = [
{
# NOTE: A single typemap entry can list multiple individual type mappings.
# Each mapping assumes the same values for |traits_headers| etc below.
#
# To typemap a type with separate |traits_headers| etc, add a separate
# entry to |cpp_typemaps|.
types = [
{
mojom = "gfx.mojom.Rect"
cpp = "::gfx::Rect"
},
]
traits_headers = [ "//ui/gfx/geometry/mojo/geometry_mojom_traits.h" ]
traits_sources = [ "//ui/gfx/geometry/mojo/geometry_mojom_traits.cc" ]
traits_public_deps = [ "//ui/gfx/geometry" ]
},
]
}
Let's look at each of the variables above:
-
mojom
: Specifies themojom
file to which the typemap applies. Many typemaps may apply to the samemojom
file, but any given typemap may only apply to a singlemojom
file. -
public_headers
: Additional headers required by any code which would depend on the Mojom definition ofgfx.mojom.Rect
now that the typemap is applied. Any headers required for the native target type definition should be listed here. -
traits_headers
: Headers which contain the relevantStructTraits
specialization(s) for any type mappings described by this file. -
sources
: Any implementation sources and headers needed for theStructTraits
definition. These sources are compiled directly into the generated C++ bindings target for amojom
file applying this typemap. -
public_deps
: Target dependencies exposed by thepublic_headers
andtraits_headers
. -
deps
: Target dependencies exposed bysources
but not already covered bypublic_deps
. -
type_mappings
: A list of type mappings to be applied for this typemap. The strings in this list are of the format"MojomType=CppType"
, whereMojomType
must be a fully qualified Mojom typename andCppType
must be a fully qualified C++ typename. Additional attributes may be specified in square brackets following theCppType
:-
move_only
: TheCppType
is move-only and should be passed by value in any generated method signatures. Note thatmove_only
is transitive, so containers ofMojomType
will translate to containers ofCppType
also passed by value. -
copyable_pass_by_value
: Forces values of typeCppType
to be passed by value without moving them. Unlikemove_only
, this is not transitive. -
nullable_is_same_type
: By default a non-nullableMojomType
will be mapped toCppType
while a nullableMojomType?
will be mapped tobase::Optional<CppType>
. If this attribute is set, thebase::Optional
wrapper is omitted for nullableMojomType?
values, but theStructTraits
definition for this type mapping must define additionalIsNull
andSetToNull
methods. See Specializing Nullability below. -
force_serialize
: The typemap is incompatible with lazy serialization (e.g. consider a typemap to abase::StringPiece
, where retaining a copy is unsafe). Any messages carrying the type will be forced down the eager serailization path.
-
Now that we have the typemap file we need to add it to a local list of typemaps that can be added to the global configuration. We create a new //ui/gfx/typemaps.gni
file with the following contents:
typemaps = [ "//ui/gfx/geometry/mojo/geometry.typemap",]
And finally we can reference this file in the global default (Chromium) bindings configuration by adding it to _typemap_imports
in chromium_bindings_configuration.gni:
_typemap_imports = [ ..., "//ui/gfx/typemaps.gni", ...,]
类型映射没有traits_sources
在类型映射配置中使用traits_sources
意味着列出的源将直接烘焙到相应mojom
目标自己的源中。如果你想对 Blink 和非 Blink 绑定使用相同的类型映射,这可能会有问题。
对于这种情况,建议您为component
类型映射特征定义一个单独的目标,并在traits_public_deps
类型映射中引用它:
mojom("mojom") {
sources = [
"rect.mojom",
]
cpp_typemaps = [
{
types = [
{
mojom = "gfx.mojom.Rect"
cpp = "::gfx::Rect"
},
]
traits_headers = [ "//ui/gfx/geometry/mojo/geometry_mojom_traits.h" ]
traits_public_deps = [ ":geometry_mojom_traits" ]
},
]
}
component("geometry_mojom_traits") {
sources = [
"//ui/gfx/geometry/mojo/geometry_mojom_traits.cc",
"//ui/gfx/geometry/mojo/geometry_mojom_traits.h",
]
# The header of course needs corresponding COMPONENT_EXPORT() tags.
defines = [ "IS_GEOMETRY_MOJOM_TRAITS_IMPL" ]
}
结构特征参考
每个StructTraits
专业化的静态 getter 方法------每个结构字段一个------必须返回一个类型,该类型可以在序列化期间用作该字段的数据源。这是一个快速参考,将 Mojom 字段类型映射到有效的 getter 返回类型
|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Mojom Field Type | C++ Getter Return Type |
| bool | bool |
| int8 | int8_t |
| uint8 | uint8_t |
| int16 | int16_t |
| uint16 | uint16_t |
| int32 | int32_t |
| uint32 | uint32_t |
| int64 | int64_t |
| uint64 | uint64_t |
| float | float |
| double | double |
| handle | mojo::ScopedHandle |
| handle<message_pipe> | mojo::ScopedMessagePipeHandle |
| handle<data_pipe_consumer> | mojo::ScopedDataPipeConsumerHandle |
| handle<data_pipe_producer> | mojo::ScopedDataPipeProducerHandle |
| handle<platform> | mojo::PlatformHandle |
| handle<shared_buffer> | mojo::ScopedSharedBufferHandle |
| pending_remote<Foo> | mojo::PendingRemote<Foo> |
| pending_receiver<Foo> | mojo::PendingReceiver<Foo> |
| pending_associated_remote<Foo> | mojo::PendingAssociatedRemote<Foo> |
| pending_associated_receiver<Foo> | mojo::PendingAssociatedReceiver<Foo> |
| string | Value or reference to any type T that has a mojo::StringTraits specialization defined. By default this includes std::string, base::StringPiece, and WTF::String (Blink). |
| array<T> | Value or reference to any type T that has a mojo::ArrayTraits specialization defined. By default this includes std::array<T, N>, std::vector<T>, WTF::Vector<T> (Blink), etc. |
| array<T, N> | Similar to the above, but the length of the data must be always the same as N. |
| map<K, V> | Value or reference to any type T that has a mojo::MapTraits specialization defined. By default this includes std::map<T>, mojo::unordered_map<T>, WTF::HashMap<T> (Blink), etc. |
| FooEnum | Value of any type that has an appropriate EnumTraits specialization defined. By default this includes only the generated FooEnum type. |
| FooStruct | Value or reference to any type that has an appropriate StructTraits specialization defined. By default this includes only the generated FooStructPtr type. |
| FooUnion | Value of reference to any type that has an appropriate UnionTraits specialization defined. By default this includes only the generated FooUnionPtr type. |
| Foo? | absl::optional<CppType>, where CppType is the value type defined by the appropriate traits class specialization (e.g. StructTraits, mojo::MapTraits, etc.). This may be customized by the typemapping. |
使用生成的 DataView 类型
专门化的静态Read
方法StructTraits
获得一个生成的FooDataView
参数(如上RectDataView
例中的 ),它在传入消息的内容中公开序列化 Mojom 结构的直接视图。为了使其尽可能易于使用,生成的FooDataView
类型具有对应于每个结构字段的生成方法:
-
对于 POD 字段类型(例如 布尔值、浮点数、整数),这些是简单的访问器方法,其名称与字段名称相同。因此在这个
Rect
例子中我们可以访问像data.x()
和这样的东西data.width()
。返回类型与上表中StructTraits Reference下列出的映射完全对应。 -
对于句柄和接口类型(例如
handle
orpending_remote<Foo>
),它们被命名TakeFieldName
(对于一个名为 的字段field_name
),它们按值返回一个适当的只能移动的句柄类型。返回类型与上表中StructTraits Reference下列出的映射完全对应。 -
对于所有其他字段类型(例如 ,枚举、字符串、数组、映射、结构),这些被命名
ReadFieldName
(对于名为的字段field_name
)并且它们返回一个bool
(以指示读取成功或失败)。成功时,他们用反序列化的字段值填充输出参数。输出参数可以是指向具有适当特化定义的任何类型的指针StructTraits
,如上表中StructTraits Reference中所述。
一个例子在这里很有用。假设我们引入了一个新的 Mojom 结构:
struct RectPair {
Rect left;
Rect right;
};
and a corresponding C++ type:
class RectPair {
public:
RectPair() {}
const gfx::Rect& left() const { return left_; }
const gfx::Rect& right() const { return right_; }
void Set(const gfx::Rect& left, const gfx::Rect& right) {
left_ = left;
right_ = right;
}
// ... some other stuff
private:
gfx::Rect left_;
gfx::Rect right_;
};
Our traits to map gfx::mojom::RectPair to gfx::RectPair might look like this:
namespace mojo {
template <>
class StructTraits
public:
static const gfx::Rect& left(const gfx::RectPair& pair) {
return pair.left();
}
static const gfx::Rect& right(const gfx::RectPair& pair) {
return pair.right();
}
static bool Read(gfx::mojom::RectPairDataView data, gfx::RectPair* out_pair) {
gfx::Rect left, right;
if (!data.ReadLeft(&left) || !data.ReadRight(&right))
return false;
out_pair->Set(left, right);
return true;
}
} // namespace mojo
Generated ReadFoo methods always convert multi_word_field_name fields to ReadMultiWordFieldName methods.
Variants
By now you may have noticed that additional C++ sources are generated when a Mojom is processed. These exist due to type mapping, and the source files we refer to throughout this docuemnt (namely foo.mojom.cc
and foo.mojom.h
) are really only one variant (the default or chromium variant) of the C++ bindings for a given Mojom file.
The only other variant currently defined in the tree is the blink variant, which produces a few additional files:
out/gen/sample/db.mojom-blink.cc out/gen/sample/db.mojom-blink.h
These files mirror the definitions in the default variant but with different C++ types in place of certain builtin field and parameter types. For example, Mojom strings are represented by WTF::String
instead of std::string
. To avoid symbol collisions, the variant's symbols are nested in an extra inner namespace, so Blink consumer of the interface might write something like:
#include "sample/db.mojom-blink.h" class TableImpl : public db::mojom::blink::Table { public: void AddRow(int32_t key, const WTF::String& data) override { // ... } };
In addition to using different C++ types for builtin strings, arrays, and maps, the custom typemaps applied to Blink bindings are managed separately from regular bindings.
mojom
targets support a blink_cpp_typemaps
parameter in addition to the regular cpp_typemaps
. This lists the typemaps to apply to Blink bindings.
To depend specifically on generated Blink bindings, reference ${target_name}_blink
. So for example, with the definition:
In //foo/mojom mojom("mojom") { sources = [ "db.mojom", ] }
C++ sources can depend on the Blink bindings by depending on "//foo/mojom:mojom_blink"
.
Finally note that both bindings variants share some common definitions which are unaffected by differences in the type-mapping configuration (like enums, and structures describing serialized object formats). These definitions are generated in shared sources:
out/gen/sample/db.mojom-shared.cc out/gen/sample/db.mojom-shared.h out/gen/sample/db.mojom-shared-internal.h
Including either variant's header (db.mojom.h
or db.mojom-blink.h
) implicitly includes the shared header, but may wish to include only the shared header in some instances.
C++ sources can depend on shared sources only, by referencing the "${target_name}_shared"
target, e.g. "//foo/mojom:mojom_shared"
in the example above.
For converting between Blink and non-Blink variants, please see //third_party/blink/public/platform/cross_variant_mojo_util.h
.
Versioning Considerations
For general documentation of versioning in the Mojom IDL see Versioning.
This section briefly discusses some C++-specific considerations relevant to versioned Mojom types.
Querying Interface Versions
Remote
defines the following methods to query or assert remote interface version:
void QueryVersion(base::OnceCallback<void(uint32_t)> callback);
This queries the remote endpoint for the version number of its binding. When a response is received callback
is invoked with the remote version number. Note that this value is cached by the Remote
instance to avoid redundant queries.
void RequireVersion(uint32_t version);
Informs the remote endpoint that a minimum version of version
is required by the client. If the remote endpoint cannot support that version, it will close its end of the pipe immediately, preventing any other requests from being received.
Versioned Enums
All extensible enums should have one enumerator value designated as the default using the [Default]
attribute. When Mojo deserializes an enum value that is not defined in the current version of the enum definition, that value will be transparently mapped to the [Default]
enumerator value. Implementations can use the presence of this enumerator value to correctly handle version skew.
[Extensible] enum Department { [Default] kUnknown, kSales, kDev, kResearch, };
Using Mojo Bindings in Chrome
See Converting Legacy Chrome IPC To Mojo.
Additional Documentation
Calling Mojo From Blink: A brief overview of what it looks like to use Mojom C++ bindings from within Blink code.