本部分可帮助您了解如何使用新的 C++ FIDL 绑定。如需查看有关如何设置 build 并从头开始编写简单客户端或服务器的分步指南,请参阅使用入门。如需查看有关如何有效使用绑定的更详细指南和建议,请参阅主题。 如需了解代码中经常出现的名称和概念,以及有关如何做出正确选择的简要说明,请参阅术语。
概括来讲,C++ 绑定由以下部分组成:
- 数据:
- 行为:通过协议发送这些网域对象、接收事件的客户端/服务器 API,...
自然网域对象和线网域对象
绑定支持两种类型的网域对象:自然类型和有线类型:
- 自然类型是针对人体工程学进行优化的顶级网域对象。
- 这些类型通过智能指针拥有其子项。
- 它们使用惯用的 C++ 类型,例如
std::vector、std::optional和std::string。 - 对于名为
fuchsia.my.lib的 FIDL 库,类型是在fuchsia_my_lib命名空间中生成的。
- wire 类型针对性能和就地解码进行了优化。
- 它们是专门的 C++ 标准布局类型,其内存布局与 FIDL 有线格式一致。
- 内嵌子级是指向单独缓冲区的无主指针。 请参阅有线网域对象的内存所有权。
- 对于名为
fuchsia.my.lib的 FIDL 库,类型是在fuchsia_my_lib::wire命名空间中生成的。
在开始项目时,请默认选择自然类型,因为它们更易于使用且性能合理。只有在优化关键路径中的逻辑时,或者需要精确控制内存分配时,才应使用线类型。由于 wire 类型包含不安全的视图,因此不当使用这些视图可能会导致释放后再使用和其他内存安全 bug。
使用入门
- 使用自然域对象和线域对象
- 编写服务器
- 编写客户端(异步或同步)
主题
术语
客户端和服务器中是否包含 WireWire
客户端/服务器 API 名称中存在 Wire 前缀表示该 API 仅接受线类型。否则,API 通常可以接受有线类型和自然类型,或者默认接受自然类型。例如,fidl::Client 支持使用自然类型和有线类型拨打电话,而 fidl::WireClient 公开的接口限制更多,仅接受有线类型。
fidl::Server 接收自然类型的请求,并可能会发送自然类型或有线类型的回复。另一方面,fidl::WireServer 接收线类型请求,并仅以线类型发送回复。
为了在发送端同时支持 wire 类型和自然类型,而不会出现函数重载歧义,wire 接口位于 .wire() 访问器下。例如,给定一个 fidl::Client<MyProtocol> client;,可以使用 client->SomeMethod(natural_type); 通过自然类型发出请求,也可以使用 client.wire()->SomeMethod(wire_type); 通过有线类型发出请求。
建议
使用不带 Wire 前缀的客户端/服务器 API。只有在需要确保编译时仅使用 Wire 类型时,才需要定义使用 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*)。在这种情况下,在同步客户端和异步客户端以及同步调用和异步调用之间进行选择时,请优先选择异步对应项。例如,优先选择不通过.sync()的fidl::WireClient,而不是fidl::WireSyncClient或.sync()。特别是,如果调度器是单线程的,请勿在调度器线程上进行同步调用,以免发生死锁。
如果您的代码是服务(即向其他组件提供功能的组件),则应使用异步调度程序和异步客户端来支持多个使用方所需的并发级别。
如果您的代码是供其他应用使用的库,则需要更仔细地考虑是否应公开同步或异步接口,具体取决于用户的需求。例如,使用同步客户端并公开同步接口的库将更难被高度并发的应用使用,因为这些应用会在异步调度程序上调度其工作。
以上是一般建议,不同的异步运行时可能有自己的更具体建议。
客户中 Shared 与无 shared 的对比
如果客户端类型的名称中包含“shared”,则可以在任意线程上绑定和销毁该类型。请参阅线程处理指南中的 SharedClient。它将有一个不带“shared”的对应项(例如 Client),该对应项必须在调度程序线程上绑定和销毁。WireClient 和 WireSharedClient 之间也存在类似的关系。
建议
在 Client 和 SharedClient 之间进行选择时,除非应用的线程模型或性能要求需要使用多线程客户端,否则请优先选择 Client。如需了解使用 SharedClient 时需要注意的诸多方面,请参阅线程处理指南。Client 中的额外限制旨在减少内存竞争和释放后使用。例如,如果您的所有对象都位于同一单线程异步调度程序上,则可以使用 Client。
双向通话中的 Then 与 ThenExactlyOnce
当异步调用有响应时,可以通过两种方式指定回调来接收该调用的结果:
- 使用
.ThenExactlyOnce(...)时,系统始终会调用一次回调,以传递结果。 - 使用
.Then(...)时,当客户端对象被销毁时,系统会默默舍弃回调,这适用于面向对象的代码。
Then 的动机
进行异步双向调用时,该调用的结果会在稍后(即执行已离开进行调用的原始范围之后)传送回应用。异步调度程序稍后会调用您在进行调用时指定的后续逻辑,称为延续。这意味着,对象被销毁后很容易被使用,从而导致内存损坏:
// 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保证只调用一次 continuation。如果客户端对象被销毁,则延续会收到取消错误。您需要确保在延续任务运行时,所有被引用的对象仍然处于有效状态,而延续任务的运行时间可能是在客户端对象销毁后的某个不确定的时间。如果您的延续必须只调用一次,例如在与fpromise完成器或 FIDL 服务器完成器交互时,或者在单元测试期间,您应选择ThenExactlyOnce。
建议
根据经验法则:
- 如果您的回调看起来像
client_->Foo([this],请使用Then(请注意,client_是成员变量)。 - 如果您的回调如下所示:
client->Foo([completer],或者client->Foo([],或者client->Foo([&](在单元测试中很常见),- 回调捕获弱指针或强指针,
- 使用
ThenExactlyOnce。
请勿捕获生命周期不同的对象,以免在继续运行时只有部分对象处于活动状态。
Zircon 通道传输与驱动程序传输
FIDL 协议与相应的传输相关联,该传输在 FIDL 定义中指定,用于确定可通过该协议传输的资源类型,并可能会影响用于发送和接收消息的生成的 API。C++ 绑定支持两种传输方式:
Zircon 渠道传输由端点类型 fidl::ClientEnd<SomeProtocol> 和 fidl::ServerEnd<SomeProtocol> 表示。
驱动程序传输使用端点类型 fdf::ClientEnd<SomeProtocol> 和 fdf::ServerEnd<SomeProtocol>。
竞技场
Arena 对象用于管理内存缓冲区池并提供高效的分配。 它们广泛用于有线网域对象以及有线客户端和服务器,以避免昂贵的复制操作。
竞技场不与自然网域对象以及封装了内存分配详细信息的关联客户端和服务器一起使用。
您可以使用 fidl::Arena 创建位于该 arena 上的有线网域对象。请参阅内存管理。