本文档概述了在执行下列操作时可用于管理内存的工具: 使用新 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<T>
在 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::Arena
在 lib/fidl/cpp/wire/arena.h 中定义。
这些对象首先分配在属于 arena 的缓冲区(
竞技场的内联字段)。缓冲区的默认大小为 512 字节。答
可以使用 fidl::Arena<size>
选择不同的尺寸。通过调整尺寸
可以在请求期间创建的所有分配对象都适合
从而避免开销更高的堆分配。
当内嵌缓冲区已满时, arena 会在堆上分配更多缓冲区。 每个缓冲区都是 16 KiB。如果需要一个大于 16 KiB 的对象, 将会使用有足够空间的定制缓冲区来容纳 。
使用竞技场的标准模式是:
- 定义一个类型为
fidl::Arena
的局部变量 arena。 - 使用表演场地分配对象。
- 通过调用 FIDL 方法或进行回复来发送分配的对象 完成相应流程
- 退出函数作用域后,所有这些局部变量 自动取消分配
arena 存在的时间需要比引用其中对象的所有视图类型存在的时间更长。
如需查看已添加注释的线域对象教程,请参阅线网域对象教程 展示如何在实践中利用 arena 构建表、联合等对象的示例。
创建借用无主数据的线路视图
除了托管分配策略外,还可以
直接创建指向 FIDL 无所属内存的指针。我们不建议这样做
很容易意外导致“释放后使用”错误。大多数视图类型都提供
FromExternal
工厂函数,用于明确借用指向
不受 FIDL 运行时管理。
使用以下代码根据外部对象创建 ObjectView
:
fidl::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());
使用以下代码从外部集合创建 VectorView
:
fidl::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);
使用以下命令从外部缓冲区创建 StringView
:
fidl::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");