传输域对象的内存所有权

本文档概述了在执行下列操作时可用于管理内存的工具: 使用新 C++ 绑定中的传输域对象。

有线网域对象以安全为中心换取性能

C++ 绑定的有线域对象(也称为“有线类型”)非常细 其内存布局与 源 *.fidl 类型的 FIDL 传输格式 。提供了便捷封装容器和存取器,但未提供 尝试封装底层线缆格式的结构。这个 通常可以就地访问字段,并以更少的 复制。了解 FIDL 传输格式的布局 将大大有助于与电线类型进行交互。具体而言,电线类型 不拥有其行外子项(如 FIDL 有线格式所定义)。

传输类型使用以下代码保留对对象的无主引用:

  • 对于字符串,fidl::StringView
  • 对于对象的矢量,使用 fidl::VectorView
  • 对于行外对象,使用 fidl::ObjectView
  • 对于名为 Foo 的表,这个 Foo 类会封装矢量视图,并引用 用 fidl::WireTableFrame<Foo> 表示的信封标头的集合; 反过来引用表格中的字段
  • 对于名为 Foo 的联合,Foo 类用于存储序数和 信封标头
    • 小于等于 4 字节的小型字段使用内嵌表示法,并存储在 。
    • 更大的字段 >使用行外表示法存储的 4 个字节 并由标头引用。
  • 对于 MyMethod 请求消息,会包含一个封装指针的 MyMethodRequestView 。此定义的作用域限定为 fidl::WireServer<Protocol> 类。

使用 inline 表示,这些是非拥有的视图,仅保留引用, 不管理对象生命周期。对象的生命周期 外部。这意味着所引用对象的存在时间必须比视图更长。

复制视图会为该视图内的引用添加别名。移动视图 等同于复制,并且不会清除源代码视图。

出于内存安全原因,表是不可变的。默认构造函数 table 会返回空表。要创建包含字段的表,您必须使用 构建器。表的成员可能可变,但您无法添加或移除表 成员。

为了保持表的简单性和一致性,联合也是不可变的。他们的 默认构造函数会将其置于缺失状态。发送 不存在并集,除非在库定义中将该并集标记为 optional。 如需获取成员 bar 的联合 Foo,请调用静态工厂函数 Foo::WithBar(...)。参数可以是值(对于内嵌到 信封)、值的 fidl::ObjectView(适用于较大的值)或 值的 arena 和构造函数参数。

fidl::StringView

lib/fidl/cpp/wire/string_view.h 中定义

保留对存储在缓冲区中的可变长度字符串的引用。C++ fidl_string 的封装容器。不拥有内容的内存。

fidl::StringView 可通过提供 应分别采用 UTF-8 字节(不包括尾随 \0)。或者,您可以将 C++ 字符串字面量或任何实现 [const] char* data() 的值 和 size()。字符串视图会借用容器的内容。

它是与 fidl_string 兼容的内存布局。

fidl::VectorView&lt;T&gt;

lib/fidl/cpp/wire/vector_view.h 中定义

保留对存储在 缓冲区。fidl_vector 的 C++ 封装容器。没有元素的内存。

fidl::VectorView 可通过提供 元素。或者,您可以将任何支持 std::data,例如 标准容器或数组。矢量视图会借用 容器。

它是与 fidl_vector 兼容的内存布局。

fidl::数组<T, N>

lib/fidl/cpp/wire/array.h 中定义

拥有固定长度的元素数组。它与 std::array<T, N> 类似,但 旨在实现与 FIDL 数组和标准布局兼容的内存布局。 析构函数会在适用的情况下关闭句柄,例如句柄 fidl::Array

请求/响应处理程序中的消息视图

服务器实现中的请求处理程序接收请求的视图 消息。它们不具备支持视图的缓冲区。

请求视图背后的数据只保证在 方法处理程序。因此,如果服务器希望异步进行回复, 并且回复使用请求消息,则用户需要将相关的 从请求消息发送到自有存储空间:

// A FIDL method called "StartGame".
virtual void StartGame(
    StartGameRequestView request, StartGameCompleter::Sync completer) {
  // Suppose the request has a `foo` field that is a string view,
  // we need to copy it to an owning type e.g. |std::string|.
  auto foo = std::string(request->foo.get());
  // Make an asynchronous reply using the owning type.
  async::PostDelayedTask(
      dispatcher_,
      [foo = std::move(foo), completer = completer.ToAsync()]() mutable {
        // As an example, we simply echo back the string.
        completer.Reply(fidl::StringView::FromExternal(foo));
      });
}

同样,传递给客户端的响应处理程序和事件处理程序也仅 接收响应/事件消息的视图。“复制到用户拥有的存储空间”问题 如果需要在处理程序返回后访问它们,则必需:

// Suppose the response has a `bar` field that is a table:
//
// type Bar = table {
//     1: a uint32;
//     2: b string;
// };
//
// we need to copy the table to an owned type by copying each element.
struct OwnedBar {
  std::optional<uint32_t> a;
  std::optional<std::string> b;
};
// Suppose we are in a class that has a `OwnedBar bar_` member.
client_->MakeMove(args).Then([](fidl::WireUnownedResult<TicTacToe::MakeMove>& result) {
  assert(result.ok());
  auto* response = result.Unwrap();
  // Create an owned value and copy the wire table into it.
  OwnedBar bar;
  if (response->bar.has_a())
    bar.a = response->bar.a();
  if (response->bar.has_b())
    bar.b = std::string(response->bar.b().get());
  bar_ = std::move(bar);
});

创建电线视图和对象

概括来讲,创建电线对象有两种方法:使用竞技场,或 以不安全的方式借用内存使用场地不仅安全,而且性能出色 案例以不安全的方式借用内存非常容易出错和损坏, 在需要控制分配的每个字节时调用 。

利用竞技场制作电线物体

有线对象与 Area 接口 fidl::AnyArena 集成,通常在 构造函数或工厂函数,这可让用户注入自定义 分配行为FIDL 运行时提供 Area 接口,fidl::Arena。Area 管理分配的 导线对象(它拥有这些对象)。一旦竞技场被摧毁, 其分配的对象会被取消分配,并调用其析构函数。

fidl::Arenalib/fidl/cpp/wire/arena.h 中定义。

这些对象首先分配在属于 arena 的缓冲区( 竞技场的内联字段)。缓冲区的默认大小为 512 字节。答 可以使用 fidl::Arena<size> 选择不同的尺寸。通过调整尺寸 可以在请求期间创建的所有分配对象都适合 从而避免开销更高的堆分配。

当内嵌缓冲区已满时, arena 会在堆上分配更多缓冲区。 每个缓冲区都是 16 KiB。如果需要一个大于 16 KiB 的对象, 将会使用有足够空间的定制缓冲区来容纳 。

使用竞技场的标准模式是:

  • 定义一个类型为 fidl::Arena 的局部变量 arena。
  • 使用表演场地分配对象。
  • 通过调用 FIDL 方法或进行回复来发送分配的对象 完成相应流程
  • 退出函数作用域后,所有这些局部变量 自动取消分配

arena 存在的时间需要比引用其中对象的所有视图类型存在的时间更长。

如需查看已添加注释的线域对象教程,请参阅线网域对象教程 展示如何在实践中利用 arena 构建表、联合等对象的示例。

创建借用无主数据的线路视图

除了托管分配策略外,还可以 直接创建指向 FIDL 无所属内存的指针。我们不建议这样做 很容易意外导致“释放后使用”错误。大多数视图类型都提供 FromExternal 工厂函数,用于明确借用指向 不受 FIDL 运行时管理。

使用以下代码根据外部对象创建 ObjectViewfidl::ObjectView<T>::FromExternal

fidl::StringView str("hello");
// |object_view| is a view that borrows the string view.
// Destroying |str| will invalidate |object_view|.
fidl::ObjectView object_view = fidl::ObjectView<fidl::StringView>::FromExternal(&str);
// |object_view| may be dereferenced to access the pointee.
ASSERT_EQ(object_view->begin(), str.begin());

使用以下代码从外部集合创建 VectorViewfidl::VectorView<T>::FromExternal

std::vector<uint32_t> vec = {1, 2, 3, 4};
// |vv| is a view that borrows the vector contents of |vec|.
// Destroying the contents in |vec| will invalidate |vv|.
fidl::VectorView<uint32_t> vv = fidl::VectorView<uint32_t>::FromExternal(vec);
ASSERT_EQ(vv.count(), 4UL);

使用以下命令从外部缓冲区创建 StringViewfidl::StringView::FromExternal

std::string string = "hello";
// |sv| is a view that borrows the string contents of |string|.
// Destroying the contents in |string| will invalidate |sv|.
fidl::StringView sv = fidl::StringView::FromExternal(string);
ASSERT_EQ(sv.size(), 5UL);

也可以直接通过字符串字面量创建 StringView,无需使用 FromExternal。这是安全的,因为字符串字面量 静态生命周期

fidl::StringView sv1 = "hello world";
fidl::StringView sv2("Hello");
ASSERT_EQ(sv1.size(), 11UL);
ASSERT_EQ(sv2.size(), 5UL);

要创建借用外部存储的成员的电线并集,请在调用时传递 ObjectView,将成员引用到相应联合工厂函数:

fidl::StringView sv = "hello world";
fuchsia_examples::wire::JsonValue val = fuchsia_examples::wire::JsonValue::WithStringValue(
    fidl::ObjectView<fidl::StringView>::FromExternal(&sv));
ASSERT_TRUE(val.is_string_value());

连线表存储了对 fidl::WireTableFrame<SomeTable>(即 负责跟踪字段元数据。制作电线表 借用外部帧,将 ObjectView 传递给 ExternalBuilder

以下示例展示了如何设置内嵌到框架中的字段:

fidl::WireTableFrame<fuchsia_examples::wire::User> frame;
// Construct a table creating a builder borrowing the |frame|.
auto builder = fuchsia_examples::wire::User::ExternalBuilder(
    fidl::ObjectView<fidl::WireTableFrame<fuchsia_examples::wire::User>>::FromExternal(&frame));
// Small values <= 4 bytes are inlined inside the frame of the table.
builder.age(30);
// The builder is turned into an actual instance by calling |Build|.
auto user = builder.Build();
ASSERT_FALSE(user.IsEmpty());
ASSERT_EQ(user.age(), 30);

以下示例展示了如何设置与框架外行存储的字段:

fidl::WireTableFrame<fuchsia_examples::wire::User> frame;
// Construct a table creating a builder borrowing the |frame|.
auto builder = fuchsia_examples::wire::User::ExternalBuilder(
    fidl::ObjectView<fidl::WireTableFrame<fuchsia_examples::wire::User>>::FromExternal(&frame));
// Larger values > 4 bytes are still stored out of line, i.e. outside the
// frame of the table. One needs to make an |ObjectView| pointing to larger
// fields separately, using an arena or with unsafe borrowing here.
fidl::StringView str("hello");
fidl::ObjectView object_view = fidl::ObjectView<fidl::StringView>::FromExternal(&str);
builder.name(object_view);
// The builder is turned into an actual instance by calling |Build|.
auto user = builder.Build();
ASSERT_FALSE(user.IsEmpty());
ASSERT_TRUE(user.has_name());
ASSERT_EQ(user.name().get(), "hello");