chrome-mojo C++ Bindings API

news2025/2/11 22:25:21

概述

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>实际上没有做任何事情。

创建接口管道

一种方法是手动创建管道并用强类型对象包裹每一端:

#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向管道写入了一条消息。

但正如上面提到的,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:

下图说明了以下事件序列,所有事件都由上面的代码行启动:

  1. LoggerImpl调用构造函数,将传递PendingReceiver<Logger>Receiver

  2. Receiver获取 的管道端点的所有权并PendingReceiver<Logger>开始观察它的可读性。Log管道是立即可读的,因此安排了一个任务来尽快从管道中读取待处理的消息。

  3. 消息Log被读取和反序列化,导致在其绑定上Receiver调用实现。Logger::LogLoggerImpl

因此,我们的实现最终将"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>举行的方式:

  1. 消费者拥有Remote<T>:set_disconnect_handler应该被使用。

  2. 消费者不拥有Remote<T>:根据调用者想要的行为,有两个助手。如果调用者想要确保错误处理程序运行,那么mojo::WrapCallbackWithDropHandler应该使用。如果调用者希望回调始终运行,则应mojo::WrapCallbackWithDefaultInvokeIfNotRun使用 helper。对于这两个助手,应该遵循通常的回调注意以确保回调不会在消费者被销毁后运行(例如,因为消费者的所有者比Remote<T>消费者长寿)。这包括使用base::WeakPtrbase::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

当然我们也可以发送Remotes

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));

其他接口绑定类型

上面的接口部分涵盖了最常见绑定对象类型的基本用法:RemotePendingReceiverReceiver。虽然这些类型可能是实践中最常用的类型,但还有其他几种绑定客户端和实现端接口管道的方法。

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上面类似,有时维护一组Remotes 是有用的,例如组观察某个事件的客户端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_销毁)时,所有关联的接口端点(例如,barbar_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. 这将创建有效的关联接口端点,这些端点实际上并不与其他任何东西关联。

  • 设计:Mojo 相关接口

同步调用

在决定使用同步呼叫之前仔细考虑

尽管同步调用很方便,但只要不是绝对必要,就应该避免使用它们:

  • 同步调用损害了并行性,因此损害了性能。

  • 重新进入会改变消息顺序并产生您在编码时可能从未想过的调用堆栈。这一直是一个巨大的痛苦。

  • 同步调用可能会导致死锁

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 通常意味着发生了连接错误。)

在服务端,使用带有回调的签名。原因是在某些情况下,实现可能需要做一些同步方法结果所依赖的异步工作。

注意:您还可以在客户端使用带有回调的签名来异步调用该方法。

重入

在等待同步方法调用的响应时,调用线程会发生什么?它继续处理传入的同步请求消息(即同步方法调用);阻止其他消息,包括与正在进行的同步调用不匹配的异步消息和同步响应消息。

请注意,与正在进行的同步呼叫不匹配的同步响应消息无法重新输入。那是因为它们对应于调用堆栈中向下的同步调用。因此,它们需要在堆栈展开时排队和处理。

避免死锁

请注意,重入行为不会阻止涉及异步调用的死锁。您需要避免调用序列,例如:

Read more

  • Design Proposal: Mojo Sync Methods

类型映射

在许多情况下,您可能更喜欢生成的 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必要的,但我们仍然需要向绑定生成器(以及构建系统)传授有关映射的信息。为此,我们必须向mojomGN 中的目标添加更多信息:

# 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 the mojom file to which the typemap applies. Many typemaps may apply to the same mojom file, but any given typemap may only apply to a single mojom file.

  • public_headers: Additional headers required by any code which would depend on the Mojom definition of gfx.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 relevant StructTraits specialization(s) for any type mappings described by this file.

  • sources: Any implementation sources and headers needed for the StructTraits definition. These sources are compiled directly into the generated C++ bindings target for a mojom file applying this typemap.

  • public_deps: Target dependencies exposed by the public_headers and traits_headers.

  • deps: Target dependencies exposed by sources but not already covered by public_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", where MojomType must be a fully qualified Mojom typename and CppType must be a fully qualified C++ typename. Additional attributes may be specified in square brackets following the CppType:

    • move_only: The CppType is move-only and should be passed by value in any generated method signatures. Note that move_only is transitive, so containers of MojomType will translate to containers of CppType also passed by value.

    • copyable_pass_by_value: Forces values of type CppType to be passed by value without moving them. Unlike move_only, this is not transitive.

    • nullable_is_same_type: By default a non-nullable MojomType will be mapped to CppType while a nullable MojomType? will be mapped to base::Optional<CppType>. If this attribute is set, the base::Optional wrapper is omitted for nullable MojomType? values, but the StructTraits definition for this type mapping must define additional IsNull and SetToNull methods. See Specializing Nullability below.

    • force_serialize: The typemap is incompatible with lazy serialization (e.g. consider a typemap to a base::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 TypeC++ Getter Return Type
boolbool
int8int8_t
uint8uint8_t
int16int16_t
uint16uint16_t
int32int32_t
uint32uint32_t
int64int64_t
uint64uint64_t
floatfloat
doubledouble
handlemojo::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>
stringValue 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.
FooEnumValue of any type that has an appropriate EnumTraits specialization defined. By default this includes only the generated FooEnum type.
FooStructValue or reference to any type that has an appropriate StructTraits specialization defined. By default this includes only the generated FooStructPtr type.
FooUnionValue 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下列出的映射完全对应。

  • 对于句柄和接口类型(例如 handleor pending_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.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2296533.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java: framework from BLL、DAL、IDAL、MODEL、Factory using oracle

oracel 21c sql: -- 创建 School 表 CREATE TABLE School (SchoolId CHAR(5) NOT NULL,SchoolName NVARCHAR2(500) NOT NULL,SchoolTelNo VARCHAR2(8) NULL,PRIMARY KEY (SchoolId) );CREATE OR REPLACE PROCEDURE addschool(p_school_id IN CHAR,p_school_name IN NVARCHAR2,p…

Ubuntu22.04部署deepseek大模型

Ollama 官方版 Ollama 官方版: https://ollama.com/ 若你的显卡是在Linux上面 可以使用如下命令安装 curl -fsSL https://ollama.com/install.sh | shollama命令查看 rootheyu-virtual-machine:~# ollama -h Large language model runnerUsage:ollama [flags]ollama [comman…

Redis数据库(二):Redis 常用的五种数据结构

Redis 能够做到高性能的原因主要有两个&#xff0c;一是它本身是内存型数据库&#xff0c;二是采用了多种适用于不同场景的底层数据结构。 Redis 常用的数据结构支持字符串、列表、哈希表、集合和有序集合。实现这些数据结构的底层数据结构有 6 种&#xff0c;分别是简单动态字…

网络安全溯源 思路 网络安全原理

网络安全背景 网络就是实现不同主机之间的通讯。网络出现之初利用TCP/IP协议簇的相关协议概念&#xff0c;已经满足了互连两台主机之间可以进行通讯的目的&#xff0c;虽然看似简简单单几句话&#xff0c;就描述了网络概念与网络出现的目的&#xff0c;但是为了真正实现两台主机…

BS架构(笔记整理)

楔子.基本概念 1.在网络架构中&#xff1a; 服务器通常是集中式计算资源&#xff0c;负责处理和存储数据&#xff1b;客户机是请求这些服务的终端设备&#xff0c;可能是个人电脑或移动设备&#xff1b;浏览器则是客户机上用来与服务器交互的工具&#xff0c;负责展示网页内容…

06排序 + 查找(D2_查找(D2_刷题练习))

目录 1. 二分查找-I 1.1 题目描述 1.2 解题思路 方法&#xff1a;二分法&#xff08;推荐使用&#xff09; 2. 二维数组中的查找 2.1 题目描述 2.2 解题思路 方法一&#xff1a;二分查找&#xff08;推荐使用&#xff09; 3. 寻找峰值 3.1 题目描述 3.2 解题思路 方…

C++ 设计模式 - 访问者模式

一&#xff1a;概述 访问者模式将作用于对象层次结构的操作封装为一个对象&#xff0c;并使其能够在不修改对象层次结构的情况下定义新的操作。 《设计模式&#xff1a;可复用面向对象软件的基础》一书中的访问者模式因两个原因而具有传奇色彩&#xff1a;一是因为它的复杂性&a…

海云安开发者智能助手(D10)全面接入DeepSeek,赋能开发者安全高效编码新范式

海云安正式宣布完成与DeepSeek&#xff08;深度求索&#xff09;的深度技术融合&#xff0c;旗下核心产品D10开发者智能助手全面接入DeepSeek R1模型。此次合作标志着海云安在"AI驱动开发安全"领域实现重要突破。数据显示&#xff0c;通过DeepSeek R1模型的优化与蒸馏…

分布式id探索

一、为什么要使用分布式id&#xff1f; 随着数据量增加&#xff0c;数据需要进行水平拆分&#xff0c;但表自增id无法满足唯一性&#xff1b; 二、分布式id的特点 1唯一性 2 趋势递增、单调递增&#xff08;数据库中存放的数据结构数据从小到大有序排列&#xff09;&#xff0…

【Linux系统】—— 简易进度条的实现

【Linux系统】—— 简易进度条的实现 1 回车和换行2 缓冲区3 进度条的准备代码4 第一版进度条5 第二版进度条 1 回车和换行 先问大家一个问题&#xff1a;回车换行是什么&#xff0c;或者说回车和换行是同一个概念吗&#xff1f;   可能大家对回车换行有一定的误解&#xff0…

一文学会:用DeepSeek R1/V3 + AnythingLLM + Ollama 打造本地化部署的个人/企业知识库,无须担心数据上传云端的泄露问题

文章目录 前言一、AnythingLLM 简介&基础应用1.主要特性2.下载与安装3.配置 LLM 提供商4.AnythingLLM 工作区&对话 二、AnythingLLM 进阶应用&#xff1a;知识增强使用三、AnythingLLM 的 API 访问四、小结1.聊天模式2.本地存储&向量数据库 前言 如果你不知道Olla…

开源身份和访问管理方案之keycloak(一)快速入门

文章目录 什么是IAM什么是keycloakKeycloak 的功能 核心概念client管理 OpenID Connect 客户端 Client Scoperealm roleAssigning role mappings分配角色映射Using default roles使用默认角色Role scope mappings角色范围映射 UsersGroupssessionsEventsKeycloak Policy创建策略…

C++STL(六)——list模拟

目录 本次所需实现的三个类一、结点类的模拟实现构造函数 二、迭代器类的模拟实现为什么有迭代器类迭代器类的模板参数说明构造函数运算符的重载- -运算符的重载和!运算符的重载*运算符的重载->运算符的重载引入模板第二个和第三个参数 三、list的模拟实现3.1 默认成员函数构…

HTML5--网页前端编程(下)

HTML5–网页前端编程(下) 9.常用标签下 (1)表格标签 用来展示数据,显示数据,规整条理,可读性好 基本语法 <table><tr> <td>单元格内的文字</td> <td>单元格内的文字</td>… </tr> <tr> <td>单元格内的文字&l…

Git 分布式版本控制工具使用教程

1.关于Git 1.1 什么是Git Git是一款免费、开源的分布式版本控制工具&#xff0c;由Linux创始人Linus Torvalds于2005年开发。它被设计用来处理从很小到非常大的项目&#xff0c;速度和效率都非常高。Git允许多个开发者几乎同时处理同一个项目而不会互相干扰&#xff0c;并且在…

linux部署ollama+deepseek+dify

Ollama 下载源码 curl -L https://ollama.com/download/ollama-linux-amd64.tgz -o ollama-linux-amd64.tgz sudo tar -C /usr -xzf ollama-linux-amd64.tgz启动 export OLLAMA_HOST0.0.0.0:11434 ollama serve访问ip:11434看到即成功 Ollama is running 手动安装deepseek…

torch_bmm验算及代码测试

文章目录 1. torch_bmm2. pytorch源码 1. torch_bmm torch.bmm的作用是基于batch_size的矩阵乘法,torch.bmm的作用是对应batch位置的矩阵相乘&#xff0c;比如&#xff0c; mat1的第1个位置和mat2的第1个位置进行矩阵相乘得到mat3的第1个位置mat1的第2个位置和mat2的第2个位置…

CTFHub-RCE系列wp

目录标题 引言什么是RCE漏洞 eval执行文件包含文件包含php://input读取源代码远程包含 命令注入无过滤过滤cat过滤空格过滤目录分隔符过滤运算符综合过滤练习 引言 题目共有如下类型 什么是RCE漏洞 RCE漏洞&#xff0c;全称是Remote Code Execution漏洞&#xff0c;翻译成中文…

从大规模恶意攻击 DeepSeek 事件看 AI 创新隐忧:安全可观测体系建设刻不容缓

作者&#xff1a;羿莉&#xff08;萧羿&#xff09; 全球出圈的中国大模型 DeepSeek 作为一款革命性的大型语言模型&#xff0c;以其卓越的自然语言处理能力和创新性成本控制引领行业前沿。该模型不仅在性能上媲美 OpenAI-o1&#xff0c;而且在推理模型的成本优化上实现了突破…

【学习笔记】企业数字化转型顶层设计与企业架构TOGAF9.2-第0章 导论

数据要素资产化迈入关键发展期 围绕发挥数据要素乘数作用&#xff0c;研究实施“数据要素x”行动:从供需两端发力&#xff0c;在智能制造、商贸流通、交通物流、金融服务、医疗健康等若干重点领域&#xff0c;加强场景需求牵引&#xff0c;打通流通障碍、提升供给质量&#xf…