字典功能

借助字典,您可以将多项功能分为 单个单元并作为单个功能一起路由。

字典的格式为键值对存储区,其中键是 capability name 字符串,并且值为 capability。通过 值本身也可能是另一个字典功能,可用于实现 类似目录的嵌套。

定义字典功能

如需定义新字典,您可以为其添加 capability 声明,如下所示:

capabilities: [
    {
        dictionary: "bundle",
    },
],

请阅读聚合部分,了解如何添加 capability 字典。

从一开始, 字典功能和组件中的大多数其他功能类型 协议目录。协议功能最终会托管 由与组件相关联的程序发出;protocol 功能 声明只是让组件框架知晓 协议的存在。另一方面,dictionary capability 始终 由组件框架运行时托管事实上,组件不需要 使用 program 来声明 dictionary capability。

路由字典功能

由于字典是一种集合类型,因此它们支持一组更丰富的路由 操作次数。

向父实体公开字典或 向子级提供与其他功能类似。但是 字典还支持以下其他路由操作:

  • Aggregation:将 capability 安装到定义的字典中 。
  • 嵌套:将字典汇总到字典中,并使用 用于引用内部字典中功能的路径语法。
  • 检索:从字典和路由中检索功能 也可以单独使用
  • Extension:定义包含 功能

字典一经创建便无法修改。转送字典授权 只能在读取时访问它可变性解释了 字典的可变性。

有关功能路由的更多常规信息,请参阅 顶级页面

曝光

公开字典 capability 会向组件的父项授予对它的访问权限:

{
    expose: [
        {
            dictionary: "bundle",
            from: "self",
        },
    ],
}

与其他功能一样,您可以使用 as 个关键字:

{
    expose: [
        {
            dictionary: "local-bundle",
            from: "self",
            as: "bundle",
        },
    ],
}

成为协作者

提供字典功能可向子组件授予对它的访问权限:

{
    offer: [
        {
            dictionary: "bundle",
            from: "parent",
            to: [ "#child-a", "#child-b" ],
        },
    ],
}

与其他功能一样,您可以使用 as 更改孩子看到的姓名 关键字:

{
    offer: [
        {
            dictionary: "local-bundle",
            from: "self",
            to: [ "#child-a", "#child-b" ],
            as: "bundle",
        },
    ],
}

使用

目前,该框架不支持对 dictionary capability 执行 use,因为 这样。但是,您可以从检索 字典并这样使用。

汇总

如需向您定义的字典添加 capability,您可以使用 offer 关键字,并在 to 中指定目标字典。此操作称为 聚合。定义字典的组件必须与 包含 offer

要指明您希望向目标字典添加某项功能,请使用 to 中的以下语法:

to: "self/<dictionary-name>",

其中存在针对 dictionary: "<dictionary-name>"capabilities 声明。self/ 前缀反映了一个事实,即字典 是此组件的本地属性。(这属于字典路径语法 请参阅检索中的说明)。

与其他类型的 offer 一样, 可以使用 as 关键字更改字典名称。

以下是工作中的汇总示例:

capabilities: [
    {
        dictionary: "bundle",
    },
    {
        directory: "fonts",
        rights: [ "r*" ],
        path: "/fonts",
    },
],
offer: [
    {
        protocol: "fuchsia.examples.Echo",
        from: "#echo-server",
        to: "self/bundle",
    },
    {
        directory: "fonts",
        from: "self",
        to: "self/bundle",
        as: "custom-fonts",
    },
],

嵌套

聚合的一个特殊情况是嵌套,其中功能 添加到字典中本身就是一本字典。例如:

capabilities: [
    {
        dictionary: "bundle",
    },
],
offer: [
    {
        dictionary: "gfx",
        from: "parent",
        to: "self/bundle",
    },
],

通过这种方式,可以在字典中嵌套比 。请继续阅读下一部分,了解这一点。

检索

从字典中获取一项功能以独立进行路由的行为 称为检索

检索操作有两个输入:用于检索 功能,以及该字典中某项功能的键。CML 表示 如下:

  • 字典的路径在 from 属性中提供。
    • 语法为:"<source>/<path>/<to>/<dictionary>"
    • <source> 可以是路由操作 支持。例如,offer 支持 self#<child>parent, 而 expose 支持 self#<child>
    • 其余路径段用于标识(可能是嵌套的)字典 由 <source> 路由到此组件。
    • 理解这一点的一个好方法是对普通的 非嵌套的 from 语法(如示例所述) 此处)。从概念上讲,您可以将 <source> 作为特殊的“顶级”由框架提供的字典。对于 例如,parent 是包含全部 功能由父级提供,#<child> 是字典 包含 <child> 等公开的所有功能。如果您将 路径,它只表示一个字典, 嵌套在顶层对象中更深层。
  • 命名 capability 的键是作为 capability 的值提供的 关键字(protocoldirectory 等)。这是 与为未在 字典。

此语法最容易通过示例说明:

expose: [
    {
        protocol: "fuchsia.examples.Echo",
        from: "#echo-realm/bundle",
        to: "parent",
    },
],

在本例中,此组件要求名为 bundle 的字典 由 #echo-realm 公开。from 包含此字典的路径: #echo-realm/bundle。在 bundle 字典中, 组件要检索,并且路由的是一个 protocol,其中包含 fuchsia.examples.Echo。最后,该协议将展示给 组件的父级为 fuchsia.examples.Echo(作为一项单独的 capability,而不是 作为任何包含字典的一部分)。

类似语法与 use 兼容:

use: [
    {
        protocol: "fuchsia.examples.Echo",
        from: "parent/bundle",
    },
],

在本例中,组件需要父项提供字典 包含协议 fuchsia.examples.Echobundle。没有path 所以该程序的传入命名空间中的协议路径将 为默认值/svc/fuchsia.examples.Echo

请注意,通常情况下,使用声明中 from 的默认值为 "parent",但由于我们是从所提供的字典中检索协议的 因此必须明确指定该父项为源。

由于字典可以嵌套,因此字典本身可以检索 并从字典中转出:

offer: [
    {
        dictionary: "gfx",
        from: "parent/bundle",
        to: "#echo-child",
    },
],

在此示例中,组件假定父组件向其提供字典 bundle,而 bundle 包含一个名为 gfx 的字典, 单独提供给#echo-child

from 支持任意级别的嵌套。这是 示例:

offer: [
    {
        protocol: "fuchsia.ui.Compositor",
        from: "parent/bundle/gfx",
        to: "#echo-child",
    },
],

与上个示例一样,该组件假定父组件提供一个字典, 添加到名为 bundle 的应用中,而该 bundle 包含一个名为 gfx 的字典。 最后,gfx 包含一项名为 fuchsia.ui.Compositor 的协议功能, 该方案由 #echo-child 单独提供。

最后,我们甚至可以将检索与 聚合:将功能从一个字典路由到 其他:

capabilities: [
    {
        dictionary: "my-bundle",
    },
],
offer: [
    {
        protocol: "fuchsia.examples.Echo",
        from: "parent/bundle",
        to: "self/my-bundle",
    },
],

扩展程序

在某些情况下,您可能需要构建包含功能的字典。 由多个组件添加的您无法通过汇总 因为一个组件只有一个 允许向其定义的字典添加功能。

不过,您可以通过扩展程序实现类似的功能。扩展程序 操作可让您声明一个初始内容为 从其他字典(称为“源字典”)复制过来的。这样, 您可以创建一个新字典,它基于上一个字典 而无需通过它逐个路由所有功能。

通常,在扩展字典时,您会希望添加额外的 扩展字典的功能。用于额外 功能不得与源字典中的任何键冲突。如果他们 之后,当有人尝试检索 扩展字典中的功能。

您可以将一个字典声明为另一个字典的扩展,方法是添加 extends 添加到字典中用于标识来源的 capability 声明的关键字 字典。extends 的语法与讨论的 from 的语法相同 检索

例如:

capabilities: [
    {
        dictionary: "my-bundle",
        extends: "parent/bundle",
    },
],
offer: [
    {
        protocol: "fuchsia.examples.Echo",
        from: "#echo-server",
        to: "self/my-bundle",
    },
    {
        dictionary: "my-bundle",
        from: "self",
        to: "#echo-client",
        as: "bundle",
    },
],

from 一样,extends 中的路径可以引用嵌套的字典:

capabilities: [
    {
        dictionary: "my-gfx",
        extends: "parent/bundle/gfx",
    },
],

动态字典

作为扩展程序的一个特例,源字典可以是 由组件程序在运行时动态提供的字典。 这提供了一种在运行时预配的字典和 声明式 dictionary 功能。语法如下:

extends: "program/<outgoing-dir-path>",

<outgoing-dir-path> 是组件的 传出目录传递给负责提供 fuchsia.component.sandbox/Router 协议, 预计会返回 Dictionary capability。

我们来通过一个示例来说明此功能。该示例包含 由两个部分组成

  • dynamic-dictionary-provider:创建运行时 使用三个 Echo 协议的 Dictionary 实例。它通过 Router 公开 Dictionary。 并在其 CML 中定义扩展 Routerdictionary
  • dynamic-dictionary:声明 CML 以检索三个 来自 dictionaryEcho 协议,并运行代码以使用以上各项 协议
提供商

dynamic-dictionary-provider 的组件清单如下所示。在本专精课程 请参阅 bundledictionary 定义,该定义会扩展 Router

    capabilities: [
        {
            dictionary: "bundle",
            path: "/svc/fuchsia.component.sandbox.Router",
        },
    ],
    use: [
        {
            protocol: [
                "fuchsia.component.sandbox.CapabilityStore",
                "fuchsia.component.sandbox.Factory",
            ],
            from: "framework",
        },
    ],
    expose: [
        {
            dictionary: "bundle",
            from: "self",
        },
    ],
}

在初始化时,dynamic-dictionary-provider 会使用 CapabilityStore 沙盒 API,用于创建新 Dictionary,然后向其中添加三个 Connector。每个 Connector 表示 Echo 协议实例之一。

let store = client::connect_to_protocol::<fsandbox::CapabilityStoreMarker>().unwrap();
let id_gen = sandbox::CapabilityIdGenerator::new();

// Create a dictionary
let dict_id = id_gen.next();
store.dictionary_create(dict_id).await.unwrap().unwrap();

// Add 3 Echo servers to the dictionary
let mut receiver_tasks = fasync::TaskGroup::new();
for i in 1..=3 {
    let (receiver, receiver_stream) =
        endpoints::create_request_stream::<fsandbox::ReceiverMarker>().unwrap();
    let connector_id = id_gen.next();
    store.connector_create(connector_id, receiver).await.unwrap().unwrap();
    store
        .dictionary_insert(
            dict_id,
            &fsandbox::DictionaryItem {
                key: format!("fidl.examples.routing.echo.Echo-{i}"),
                value: connector_id,
            },
        )
        .await
        .unwrap()
        .unwrap();
    receiver_tasks.spawn(async move { handle_echo_receiver(i, receiver_stream).await });
}

每个 Connector 都绑定到一个 Receiver,用于处理 传入的 Echo 请求。Receiver 处理程序的实现如下: 与 ServiceFs 处理程序非常相似,不同之处在于 ServiceFsReceiver 会绑定到 Connector,而不是 组件的传出目录

async fn handle_echo_receiver(index: u64, mut receiver_stream: fsandbox::ReceiverRequestStream) {
    let mut task_group = fasync::TaskGroup::new();
    while let Some(request) = receiver_stream.try_next().await.unwrap() {
        match request {
            fsandbox::ReceiverRequest::Receive { channel, control_handle: _ } => {
                task_group.spawn(async move {
                    let server_end = endpoints::ServerEnd::<EchoMarker>::new(channel.into());
                    run_echo_server(index, server_end.into_stream().unwrap()).await;
                });
            }
            fsandbox::ReceiverRequest::_UnknownMethod { ordinal, .. } => {
                warn!(%ordinal, "Unknown Receiver request");
            }
        }
    }
}

async fn run_echo_server(index: u64, mut stream: EchoRequestStream) {
    while let Ok(Some(event)) = stream.try_next().await {
        let EchoRequest::EchoString { value, responder } = event;
        let res = match value {
            Some(s) => responder.send(Some(&format!("{s} {index}"))),
            None => responder.send(None),
        };
        if let Err(err) = res {
            warn!(%err, "Failed to send echo response");
        }
    }
}

最后,我们需要使用 Router

let mut fs = ServiceFs::new_local();
fs.dir("svc").add_fidl_service(IncomingRequest::Router);
fs.take_and_serve_directory_handle().unwrap();
fs.for_each_concurrent(None, move |request: IncomingRequest| {
    let store = store.clone();
    let id_gen = id_gen.clone();
    async move {
        match request {
            IncomingRequest::Router(mut stream) => {
                while let Ok(Some(request)) = stream.try_next().await {
                    match request {
                        fsandbox::RouterRequest::Route { payload: _, responder } => {
                            let dup_dict_id = id_gen.next();
                            store.duplicate(dict_id, dup_dict_id).await.unwrap().unwrap();
                            let capability = store.export(dup_dict_id).await.unwrap().unwrap();
                            let _ = responder.send(Ok(capability));
                        }
                        fsandbox::RouterRequest::_UnknownMethod { ordinal, .. } => {
                            warn!(%ordinal, "Unknown Router request");
                        }
                    }
                }
            }
        }
    }
})
.await;

Router 请求处理程序会导出字典和 返回。请注意,这会使 字典里第 CapabilityStore/Duplicate 行 第一,因为框架可能会多次调用 Router/Route。它包含 复制字典句柄而不复制内部 内容。

fsandbox::RouterRequest::Route { payload: _, responder } => {
    let dup_dict_id = id_gen.next();
    store.duplicate(dict_id, dup_dict_id).await.unwrap().unwrap();
    let capability = store.export(dup_dict_id).await.unwrap().unwrap();
    let _ = responder.send(Ok(capability));
}
客户端

客户端很简单。首先,组件清单会检索 bundle 字典中的协议(使用常规检索) 语法。

use: [
    {
        protocol: [
            "fidl.examples.routing.echo.Echo-1",
            "fidl.examples.routing.echo.Echo-2",
            "fidl.examples.routing.echo.Echo-3",
        ],
        from: "#provider/bundle",
    },
],

该程序仅依次连接到每个协议并尝试使用该协议:

for i in 1..=3 {
    info!("Connecting to Echo protocol {i} of 3");
    let echo = client::connect_to_protocol_at_path::<EchoMarker>(&format!(
        "/svc/fidl.examples.routing.echo.Echo-{i}"
    ))
    .unwrap();
    let res = echo.echo_string(Some(&format!("hello"))).await;
    assert_matches!(res, Ok(Some(s)) if s == format!("hello {i}"));
}

可变性

dictionary 功能遵循以下可变性规则:

  • 只有定义字典的组件才能执行以下操作: 汇总扩展数据。此外,这是 向 dictionary 添加 capability 的唯一方式, 可以在运行时进行修改
  • 路由到字典的任何组件都可以检索 功能。
  • 如果组件发出两个连续的路由请求, 从同一个 dictionary 检索,可能每个请求都会 就会返回不同的结果。例如,一个请求可能会成功,而 就会出现其他故障。这是功能的按需特性的副作用 路由。在两个请求中,某个组件可能 向上游重新解析并更改了 。