本部分将帮助您了解如何使用新的 C++ FIDL 绑定。请参阅使用入门,获取有关如何从零开始设置构建并编写简单客户端或服务器的分步指南。如需查看有关有效使用绑定的更多相关指南和建议,请参阅主题。如需了解代码中频繁出现的名称和概念,以及有关如何做出正确选择的快速说明,请参阅术语。
概括来讲,C++ 绑定由以下部分组成:
自然和有线域对象
绑定支持两种类型的网域对象:自然类型和有线类型:
- natural 类型是针对工效学设计进行优化的高级别领域对象。
- 这些类型拥有其采用智能指针的子项。
- 它们使用惯用的 C++ 类型,如
std::vector
、std::optional
和std::string
。 - 如果有一个 FIDL 库名为
fuchsia.my.lib
,类型将在fuchsia_my_lib
命名空间中生成。
- wire 类型针对性能和就地解码进行了优化。
- 它们是专用的 C++ 标准布局类型,其内存布局符合 FIDL 传输格式。
- 外行子项是指向单独缓冲区的无主指针。请参阅传输域对象的内存所有权。
- 如果有一个 FIDL 库名为
fuchsia.my.lib
,类型将在fuchsia_my_lib::wire
命名空间中生成。
启动项目时,请默认选择自然类型,因为它们更易于使用且性能合理。只有在优化关键路径中的逻辑或者需要精确控制内存分配时,才应使用电线类型。由于线类型包含不安全的视图,因此使用不当可能会导致释放后使用 bug 和其他内存安全 bug。
使用入门
- 使用自然网域对象和有线网域对象
- 编写服务器
- 编写客户端(async或同步)
主题
术语
Wire
(与没有 Wire
的客户端和服务器相比)
如果客户端/服务器 API 名称中存在 Wire
前缀,则表示该 API 仅接受传输类型。否则,该 API 通常可以同时接受线上和自然类型,或默认接受自然类型。例如,fidl::Client
支持同时使用自然类型和有线类型进行调用,而 fidl::WireClient
公开限制性更强的接口,仅接受导线类型。
fidl::Server
接收自然类型的请求,并且可以发送自然类型或有线类型的回复。另一方面,fidl::WireServer
会以传输类型接收请求,并只发送传输类型的回复。
为了在发送端同时支持有线和自然类型,而又不会产生函数过载歧义,有线接口位于 .wire()
存取器下。例如,假设有一个 fidl::Client<MyProtocol> client;
,您可以写入 client->SomeMethod(natural_type);
以使用自然类型发出请求,并写入 client.wire()->SomeMethod(wire_type);
以使用传输类型发出请求。
建议
使用不带 Wire
前缀的客户端/服务器 API。只有在编译时需要确保仅使用传输类型时,才能定义使用 Wire
对应项的函数签名,例如 fidl::WireClient
。此外,也可以仅依赖于绑定的电线部分,方法是根据 fuchsia.my.lib_cpp_wire
目标而不是 fuchsia.my.lib_cpp
GN 目标。
Sync
- 客户端中没有 sync
同步(简称“sync”)适用于有响应的 FIDL 调用(双向调用),这意味着调用阻塞:进行此类调用的线程在响应返回之前不会从调用返回。例如,fidl::WireSyncClient
是一个客户端,其中所有双向调用都是同步的。同样,fidl::WireClient
有一个 .sync()
访问器,它返回一个用于进行同步调用的接口。
单向调用没有响应,因此同步的概念不适用于它们。
建议
如果您的代码是一个仅使用其他组件功能的独立程序,请确定其业务需求所需的并发级别:
如果它不管理大量并发操作,那么您可以使用同步客户端,这样可以轻松读取直线逻辑。例如,短期运行的命令行工具可以使用
fidl::SyncClient
。如果您的代码管理大量并发操作,则它通常可以使用异步调度程序 (
async_dispatcher_t*
)。在同步和异步客户端以及调用之间进行选择时,请优先选用异步调度程序。例如,请优先使用fidl::WireClient
,而不是通过.sync()
而不是fidl::WireSyncClient
或.sync()
。特别是,如果调度程序为单线程,请勿在调度程序线程上进行同步调用,以免发生死锁。
如果您的代码是一项服务(即为其他组件提供功能的组件),它应使用异步调度程序和异步客户端来支持多个使用方所需的并发级别。
如果您的代码是其他应用使用的库,则需要更仔细地考虑应公开同步接口还是异步接口,具体取决于用户的需求。例如,使用同步客户端并公开同步接口的库对于将工作安排在异步调度程序上的高并发应用更难以使用。
以上只是一般性建议,不同的异步运行时可能都有各自的更具体的建议。
Shared
- 客户端中没有 shared
当某个客户端类型的名称中包含“共享”字样时,它便可在任意线程上绑定和销毁。请参阅线程指南中的 SharedClient
。它将具有一个没有“共享”的对应项(例如 Client
),它必须在调度程序线程上绑定并销毁。WireClient
和 WireSharedClient
之间存在类似关系。
建议
在 Client
和 SharedClient
之间进行选择时,请首选 Client
,除非应用的线程模型或性能要求需要以多线程模式使用客户端。如需了解使用 SharedClient
时的许多注意事项,请参阅线程处理指南。Client
中的额外限制旨在减少内存争用和释放后使用。例如,如果您的对象都位于同一个单线程异步调度程序上,则可以使用 Client
。
双向通话中的 Then
对阵 ThenExactlyOnce
如果异步调用有响应,您可以通过两种方式指定回调以接收该调用的结果:
- 当您使用
.ThenExactlyOnce(...)
时,回调始终被调用一次,并给出结果。 - 当您使用
.Then(...)
时,回调会在客户端对象被销毁时被静默舍弃,这适用于面向对象的代码。
使用Then
的动力
在进行异步双向调用时,在执行离开发出调用的原始范围后,该调用的结果稍后会传送回应用。异步调度程序稍后会调用您在发出调用时指定的后续逻辑,称为“接续”。continuation这意味着,对象在销毁后很容易使用,从而导致内存损坏:
// The following snippet shows an example of use-after-free
// occurring in asynchronous two-way calls.
void Foo(fidl::WireClient<MyProtocol>& client) {
bool call_ok;
client->SomeMethod().Then(
// The following lambda function represents the continuation.
[&call_ok] (fidl::WireUnownedResult<SomeMethod>& result) {
// `call_ok` has already gone out of scope.
// This would lead to memory corruption.
call_ok = result.ok();
});
}
当接续对象捕获 this
指针,并且所引用的对象也拥有客户端时,会发生这种损坏的一种更危险的方式。销毁外部对象(进而销毁客户端)会导致所有待处理的双向调用失败。当其接续运行时,它捕获的 this
指针不再有效。
Then
和 ThenExactlyOnce
都注册双向调用的延续。不过,Then
旨在减少上述损坏情况。具体而言:
Then
可确保提供的接续被最多调用一次,直到客户端被销毁。如果您的接续操作仅捕获与客户端具有相同生命周期的对象(例如,您的用户对象拥有客户端),则应选择Then
。销毁用户对象会停用任何未完成的回调。无需担心释放后使用。另一方面,
ThenExactlyOnce
保证只调用一次接续。如果客户端对象被销毁,则接续操作会收到取消错误。您需要确保所有引用的对象在接续运行时(可能是客户端对象销毁后的未指定时间)仍处于活动状态。如果您的接续必须调用一次(例如在与fpromise
完成器或 FIDL 服务器完成器对接时),或者在单元测试期间,您应选择ThenExactlyOnce
。
建议
一般来讲:
- 如果您的回调类似于
client_->Foo([this]
,请使用Then
(请注意,client_
是成员变量)。 - 如果您的回调如下所示:
client->Foo([completer]
,或client->Foo([]
,或client->Foo([&]
(在单元测试中很常见)- 回调会捕获弱指针或强指针;
- 使用
ThenExactlyOnce
。
请勿捕获具有不同生命周期的对象,使得在接续运行时只有一部分对象处于活动状态。
锆石通道传输与驱动程序传输
FIDL 协议与 FIDL 定义中指定的相应传输相关联,它确定可能流经该协议的资源种类,并可能会影响生成的用于发送和接收消息的 API。C++ 绑定支持两种传输:
Zircon 通道传输由端点类型 fidl::ClientEnd<SomeProtocol>
和 fidl::ServerEnd<SomeProtocol>
表示。
驱动程序传输使用端点类型 fdf::ClientEnd<SomeProtocol>
和 fdf::ServerEnd<SomeProtocol>
。
竞技场
Arenas 对象可管理内存缓冲区池并提供高效分配。它们广泛用于有线域对象,以及连接客户端和服务器,以避免昂贵的副本。
Arenas 不适用于自然域对象,以及封装了有关内存分配详细信息的关联客户端和服务器。
您可以使用 fidl::Arena
创建位于该场馆的有线网域对象。请参阅内存管理。
对线路域对象使用通过驱动程序传输协议时的协议时,应使用 fdf::Arena
对象来分配对消息进行编码所需的缓冲区。