字典可將多項功能分組為單一單位,並一起路由為單一功能。
字典的格式為鍵/值儲存庫,其中鍵是功能名稱字串,值則是功能。值本身可能是另一個字典功能,可用於實現類似目錄的巢狀結構。
定義字典功能
如要定義新的字典,請為其新增 capability 宣告,如下所示:
capabilities: [
    {
        dictionary: "bundle",
    },
],
請參閱匯總一節,瞭解如何在字典中新增功能。
從一開始,我們就必須指出字典功能與元件架構中其他大部分能力類型 (例如通訊協定和目錄) 之間的重要差異。通訊協定能力最終會由與元件相關聯的程式代管;protocol 能力宣告只是讓元件架構瞭解該通訊協定的存在方式。另一方面,dictionary 功能一律由元件架構執行階段代管。事實上,元件不需要 program 即可宣告 dictionary 能力。
轉譯字典功能
由於字典是集合類型,因此比其他能力類型支援更多種路由運算。
將字典公開給父項或提供給子項的基本作業,與其他功能類似。不過,字典也支援下列額外的路由作業:
字典建立後即無法修改。將字典導向後,只會授予該字典的讀取權限。可變性會更詳細說明字典的可變性語意。
如需關於能力轉送一般資訊,請參閱頂層頁面。
公開
公開字典能力會授予元件的父項存取權:
{
    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",
        },
    ],
}
使用
目前架構不支援 use dictionary 這類功能。不過,您可以擷取字典中的個別功能,並以這種方式使用。
匯總
如要為定義的字典新增功能,請使用 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>公開的所有功能,以此類推。如果您使用其他區段擴充路徑,則只會指出在頂層字典中嵌套得更深的字典。
 
- 語法為:
- 能力命名的鍵會在轉送宣告中,以能力關鍵字的值 (protocol、directory等) 形式提供。這與未在字典中轉送的功能命名語法相同。
這個語法最容易透過範例說明:
expose: [
    {
        protocol: "fuchsia.examples.Echo",
        from: "#echo-realm/bundle",
        to: "parent",
    },
],
在這個範例中,此元件預期 #echo-realm 會公開名為 bundle 的字典。from 包含字典的路徑:#echo-realm/bundle。元件想要擷取及路由的 bundle 字典功能,是具有 fuchsia.examples.Echo 鍵的 protocol。最後,這個通訊協定會以 fuchsia.examples.Echo 的形式公開給元件的父項 (做為個別功能,而非任何包含字典的一部分)。
以下類似的語法與 use 相容:
use: [
    {
        protocol: "fuchsia.examples.Echo",
        from: "parent/bundle",
    },
],
在此範例中,元件會預期父項提供包含通訊協定 fuchsia.examples.Echo 的字典 bundle。未指定 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",
    },
],
擴充功能
在某些情況下,您可能需要建立包含多個元件所新增功能的字典。您無法透過aggregating多個元件中的單一字典來執行此操作,因為元件只能將功能新增至定義的字典。
不過,您也可以透過擴充功能執行類似操作。這個擴充功能作業可讓您宣告新字典,其初始內容是從另一個字典 (稱為「來源字典」) 複製而來。透過這種方式,您可以建立新的字典,逐步建構先前的字典,而無須個別轉送其中的所有功能。
一般而言,當您擴充字典時,您會需要在擴充字典中加入其他功能。用於額外功能的所有鍵都不得與來源字典中的任何鍵衝突。如果是這樣,當使用者嘗試從擴充字典擷取功能時,就會在執行階段導致路由錯誤。
您可以將字典宣告為另一個字典的擴充字典,方法是將 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 能力,在這種情況下,字典是由元件程式在執行階段自行建立。這些字典不支援 CML 中的擴充功能或匯總;使用 fidl sandbox API 填入字典的工作則由程式負責。
capabilities: [
    {
        dictionary: "my-dynamic-dictionary",
        path: "<outgoing-dir-path>",
    },
],
<outgoing-dir-path> 是元件傳出目錄中的路徑,可連至應回傳 Dictionary 功能的 fuchsia.component.sandbox/DictionaryRouter 通訊協定。
為說明這項功能,我們來看一個範例。這個範例包含兩個元件
- dynamic-dictionary-provider:使用三個- Echo通訊協定例項,建立執行階段- Dictionary。這會透過- DictionaryRouter公開- Dictionary,並定義其支援的- dictionary。
- dynamic-dictionary:宣告 CML 以擷取- dictionary中的三個- Echo通訊協定,並執行使用這些通訊協定的程式碼。
供應商
dynamic-dictionary-provider 的元件資訊清單如下所示。我們在其中看到 bundle 的 dictionary 定義,在其 path 中命名了 DictionaryRouter。
    capabilities: [
        {
            dictionary: "bundle",
            path: "/svc/fuchsia.component.sandbox.DictionaryRouter",
        },
    ],
    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>();
    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 處理常式非常類似,但與 ServiceFs 不同,Receiver 會繫結至 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()).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");
        }
    }
}
最後,我們需要使用 DictionaryRouter 公開先前建立的字典。
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::DictionaryRouterRequest::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 fsandbox::Capability::Dictionary(dict) = capability else {
                                panic!("capability was not a dictionary? {capability:?}");
                            };
                            let _ = responder.send(Ok(
                                fsandbox::DictionaryRouterRouteResponse::Dictionary(dict),
                            ));
                        }
                        fsandbox::DictionaryRouterRequest::_UnknownMethod {
                            ordinal, ..
                        } => {
                            warn!(ordinal:%; "Unknown DictionaryRouter request");
                        }
                    }
                }
            }
        }
    }
})
.await;
DictionaryRouter 要求處理常式會匯出字典並傳回。請注意,由於架構可能會多次呼叫 DictionaryRouter.Route,因此它會先建立字典的 CapabilityStore.Duplicate。這會複製字典 handle,但不會複製內部內容。
fsandbox::DictionaryRouterRequest::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 fsandbox::Capability::Dictionary(dict) = capability else {
        panic!("capability was not a dictionary? {capability:?}");
    };
    let _ = responder.send(Ok(
        fsandbox::DictionaryRouterRouteResponse::Dictionary(dict),
    ));
}
用戶端
用戶端很簡單。首先,元件資訊清單會使用一般擷取語法,從 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 功能會遵循下列可變動性規則: