C++語言風格
C++比c語言版本稍微複雜一些。讓我們來看一下。
- banjo轉譯器生成三個文件
1、第一個文件在c語言版本中已經介紹討論過了,其他兩個文件在目錄`//zircon/build-`_TARGET_`/system/banjo/ddk-protocol-i2c/gen/include/ddktl/protocol/`。
2、`i2c.h`——你的程序需要包含的文件。
3、`i2c-internal.h`——內部文件,由i2c.h文件包含。
通常,_TARGET_ 是目標構建架構,比如x64,arm64等。
"internal"文件包含文件聲明和斷言,我們可以安全地跳過這些。
C++版本的`i2c.h`文件內容是相當的長,所以我們需要將他分成小片段來查看。下面是文件i2c.h概覽圖,顯示了從開始到每個片段的行數。
Line | Section
--------------|----------------------------
1 | [boilerplate](#a-simple-example-c-boilerplate-2)
20 | [auto generated usage comments](#auto_generated-comments)
55 | [class I2cProtocol](#the-i2cprotocol-mixin-class)
99 | [class I2cProtocolClient](#the-i2cprotocolclient-wrapper-class)
- 樣板
這份樣板的內容幾乎就是你所期望的:
[001] // Copyright 2018 The Fuchsia Authors. All rights reserved.
[002] // Use of this source code is governed by a BSD-style license that can be
[003] // found in the LICENSE file.
[004]
[005] // WARNING: THIS FILE IS MACHINE GENERATED. DO NOT EDIT.
[006] // MODIFY system/banjo/ddk-protocol-i2c/i2c.banjo INSTEAD.
[007]
[008] #pragma once
[009]
[010] #include <ddk/driver.h>
[011] #include <ddk/protocol/i2c.h>
[012] #include <ddktl/device-internal.h>
[013] #include <zircon/assert.h>
[014] #include <zircon/compiler.h>
[015] #include <zircon/types.h>
[016] #include <lib/zx/interrupt.h>
[017]
[018] #include "i2c-internal.h"
它的#include包含了一堆ddk和os的頭文件,i2c相關的包含:
C版本的頭文件(`[011]`行,這意味着所討論的所有內容[上面的C部分](#a-simple-example-c-1)也適用於此處);還有生成的`i2c-internal.h`文件,在[018]行。
接下來是自動生成的使用註釋部分;我們稍後將回到自動生成註釋的地方(#auto_generated-comments),因爲一旦我們看到實際代碼的類聲明,這個註釋它會顯得更有意義。
這兩個類的聲明被封裝在DDK的域名中,如代碼所示:
[053] namespace ddk {
...
[150]} // namespace ddk
- I2cProtocolClient封裝類
I2cProtocolClient類是i2c_protocol_t結構體的簡單封裝,該結構體定義在c頭文件中。之前我們在協議結構體中談論過此結構體。
來看下封裝類的實體代碼:
[099] class I2cProtocolClient {
[100] public:
[101] I2cProtocolClient()
[102] : ops_(nullptr), ctx_(nullptr) {}
[103] I2cProtocolClient(const i2c_protocol_t* proto)
[104] : ops_(proto->ops), ctx_(proto->ctx) {}
[105]
[106] I2cProtocolClient(zx_device_t* parent) {
[107] i2c_protocol_t proto;
[108] if (device_get_protocol(parent, ZX_PROTOCOL_I2C, &proto) == ZX_OK) {
[109] ops_ = proto.ops;
[110] ctx_ = proto.ctx;
[111] } else {
[112] ops_ = nullptr;
[113] ctx_ = nullptr;
[114] }
[115] }
[116]
[117] void GetProto(i2c_protocol_t* proto) const {
[118] proto->ctx = ctx_;
[119] proto->ops = ops_;
[120] }
[121] bool is_valid() const {
[122] return ops_ != nullptr;
[123] }
[124] void clear() {
[125] ctx_ = nullptr;
[126] ops_ = nullptr;
[127] }
[128] // Writes and reads data on an i2c channel. Up to I2C_MAX_RW_OPS operations can be passed in.
[129] // For write ops, i2c_op_t.data points to data to write. The data to write does not need to be
[130] // kept alive after this call. For read ops, i2c_op_t.data is ignored. Any combination of reads
[131] // and writes can be specified. At least the last op must have the stop flag set.
[132] // The results of the operations are returned asynchronously via the transact_cb.
[133] // The cookie parameter can be used to pass your own private data to the transact_cb callback.
[134] void Transact(const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie) const {
[135] ops_->transact(ctx_, op_list, op_count, callback, cookie);
[136] }
[137] // Returns the maximum transfer size for read and write operations on the channel.
[138] zx_status_t GetMaxTransferSize(size_t* out_size) const {
[139] return ops_->get_max_transfer_size(ctx_, out_size);
[140] }
[141] zx_status_t GetInterrupt(uint32_t flags, zx::interrupt* out_irq) const {
[142] return ops_->get_interrupt(ctx_, flags, out_irq->reset_and_get_address());
[143] }
[144]
[145] private:
[146] i2c_protocol_ops_t* ops_;
[147] void* ctx_;
[148] };
上面代碼有三個構造函數。
101行`ops_` 和 `ctx_`被賦值爲`nullptr`。
103行的初始化函數使用結構體指針`i2c_protocol_t`,構建`ops_` 和 `ctx_`的對應值。
106行另一個初始化函數,從父設備`zx_device_t`獲取`ops_` 和 `ctx_`值。
第三個構造函數是推薦使用的,使用示例如下:
ddk::I2cProtocolClient i2c(parent);
if (!i2c.is_valid()) {
return ZX_ERR_*; // return an appropriate error
}
提供了三個所需要的成員函數:
117行GetProto()獲取`ops_` 和 `ctx_`成員到對應的協議結構體中。
121行is_valid()返回一個布爾值,判斷是否該類已由一個協議初始化。
124行clear()清除`ops_` 和 `ctx_`成員的指針賦值信息。
接着,我們發現曾在banjo文件中的三個成員函數:
134行的Transact()
138行的GetMaxTransferSize()
141行的GetInterrupt()
這些工作只是喜歡包含C文件的三個包裝函數;也就是說,它們通過調用相應的函數指針將它們的參數傳遞。
事實上,對比c語言版本的i2c_get_max_transfer_size()函數:
[56] static inline zx_status_t i2c_get_max_transfer_size(const i2c_protocol_t* proto, size_t* out_size) {
[57] return proto->ops->get_max_transfer_size(proto->ctx, out_size);
[58] }
和c++版本的函數:
[138] zx_status_t GetMaxTransferSize(size_t* out_size) const {
[139] return ops_->get_max_transfer_size(ctx_, out_size);
[140] }
正如所宣傳的那樣,這個類所做的就是存儲操作和上下文指針以供以後使用,這樣通過包裝器的調用就更優雅了。您還會注意到C ++包裝器函數沒有任何名稱重整;要使用同名式,GetMaxTransferSize()是GetMaxTransferSize()。
- The I2cProtocol類
這是容易的部分。對此的下部分章節,我們將在https://en.wikipedia.org/wiki/Mixin講解。讓我們先來理解類的形狀。代碼如下:
[055] template <typename D, typename Base = internal::base_mixin>
[056] class I2cProtocol : public Base {
[057] public:
[058] I2cProtocol() {
[059] internal::CheckI2cProtocolSubclass<D>();
[060] i2c_protocol_ops_.transact = I2cTransact;
[061] i2c_protocol_ops_.get_max_transfer_size = I2cGetMaxTransferSize;
[062] i2c_protocol_ops_.get_interrupt = I2cGetInterrupt;
[063]
[064] if constexpr (internal::is_base_proto<Base>::value) {
[065] auto dev = static_cast<D*>(this);
[066] // Can only inherit from one base_protocol implementation.
[067] ZX_ASSERT(dev->ddk_proto_id_ == 0);
[068] dev->ddk_proto_id_ = ZX_PROTOCOL_I2C;
[069] dev->ddk_proto_ops_ = &i2c_protocol_ops_;
[070] }
[071] }
[072]
[073] protected:
[074] i2c_protocol_ops_t i2c_protocol_ops_ = {};
[075]
[076] private:
...
[083] static void I2cTransact(void* ctx, const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie) {
[084] static_cast<D*>(ctx)->I2cTransact(op_list, op_count, callback, cookie);
[085] }
...
[087] static zx_status_t I2cGetMaxTransferSize(void* ctx, size_t* out_size) {
[088] auto ret = static_cast<D*>(ctx)->I2cGetMaxTransferSize(out_size);
[089] return ret;
[090] }
[091] static zx_status_t I2cGetInterrupt(void* ctx, uint32_t flags, zx_handle_t* out_irq) {
[092] zx::interrupt out_irq2;
[093] auto ret = static_cast<D*>(ctx)->I2cGetInterrupt(flags, &out_irq2);
[094] *out_irq = out_irq2.release();
[095] return ret;
[096] }
[097] };
I2CProtocol類繼承自base類,base由第二個模板參數指定。如果沒有人爲指定,它將默認使用internal::base_mixin類,並且沒有特別的模數產生。然而,如果要顯示的指定該base,應該使用ddk::base_protocol類,這會把asserts檢查機制加上,雙重檢查只有一個base protocol混合型。此外,特殊的DDKTL字段設置爲在驅動程序觸發DdkAdd()時自動將此協議註冊爲基本協議。
構造函數會調用內部有效性函數,在59行CheckI2cProtocolSubclass()。定義在頭文件i2c-internal.h,它有幾種靜態斷言調用。
‘D’類被期望去實現三個成員函數使靜態方法有效工作:I2cTransact()、I2cGetMaxTransferSize()、I2cGetInterrupt()。如果沒有提供‘D’,編譯器會產生一些模板錯誤。
靜態斷言用於產生描述不清的錯誤信息,一般人不能理解。
接下來,在60~62行,指針函數(`transact`,`get_max_transfer_size`,`get_interrupt`)被綁定了。最後,如果需要,`constexpr`表達式提供默認初始化。
- 使用mixin類
I2cProtocol類可以按照如下方式使用。來自文件:`//zircon/system/dev/bus/platform/platform-proxy-device.h`。
[01] class ProxyI2c : public ddk::I2cProtocol<ProxyI2c> {
[02] public:
[03] explicit ProxyI2c(uint32_t device_id, uint32_t index, fbl::RefPtr<PlatformProxy> proxy)
[04] : device_id_(device_id), index_(index), proxy_(proxy) {}
[05]
[06] // I2C protocol implementation.
[07] void I2cTransact(const i2c_op_t* ops, size_t cnt, i2c_transact_callback transact_cb,
[08] void* cookie);
[09] zx_status_t I2cGetMaxTransferSize(size_t* out_size);
[10] zx_status_t I2cGetInterrupt(uint32_t flags, zx::interrupt* out_irq);
[11]
[12] void GetProtocol(i2c_protocol_t* proto) {
[13] proto->ops = &i2c_protocol_ops_;
[14] proto->ctx = this;
[15] }
[16]
[17] private:
[18] uint32_t device_id_;
[19] uint32_t index_;
[20] fbl::RefPtr<PlatformProxy> proxy_;
[21] };
我們看到ProxyI2c類繼承自I2cProtocol類,並將自身ProxyI2c作爲I2cProtocol的模板參數。這就是Mixin的概念。這導致`ProxyI2c`類型在類的模板定義中替換爲'D`。(來自84,88,93行的i2c.h文件)。
作爲例子,看一下I2cGetMaxTransferSize()函數,閱讀源碼發現它簡潔高效:
[087] static zx_status_t I2cGetMaxTransferSize(void* ctx, size_t* out_size) {
[088] auto ret = static_cast<ProxyI2c*>(ctx)->I2cGetMaxTransferSize(out_size);
[089] return ret;
[090] }
這最終消除了代碼中的指向自己的樣板。這種轉換是必要的,因爲類型信息在DDK邊界被擦除;回想一下上下文`ctx`是一個`void *`指針。
- 自動生成註釋
banjo會在頭文件中自動生成註釋,基本總結了寫了什麼內容。例如:
[020] // DDK i2c-protocol support
[021] //
[022] // :: Proxies ::
[023] //
[024] // ddk::I2cProtocolClient is a simple wrapper around
[025] // i2c_protocol_t. It does not own the pointers passed to it
[026] //
[027] // :: Mixins ::
[028] //
[029] // ddk::I2cProtocol is a mixin class that simplifies writing DDK drivers
[030] // that implement the i2c protocol. It doesn't set the base protocol.
[031] //
[032] // :: Examples ::
[033] //
[034] // // A driver that implements a ZX_PROTOCOL_I2C device.
[035] // class I2cDevice;
[036] // using I2cDeviceType = ddk::Device<I2cDevice, /* ddk mixins */>;
[037] //
[038] // class I2cDevice : public I2cDeviceType,
[039] // public ddk::I2cProtocol<I2cDevice> {
[040] // public:
[041] // I2cDevice(zx_device_t* parent)
[042] // : I2cDeviceType(parent) {}
[043] //
[044] // void I2cTransact(const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie);
[045] //
[046] // zx_status_t I2cGetMaxTransferSize(size_t* out_size);
[047] //
[048] // zx_status_t I2cGetInterrupt(uint32_t flags, zx::interrupt* out_irq);
[049] //
[050] // ...
[051] // };
- 使用banjo
我們還需要FIDL教程和驅動程序編寫教程之間的內容,以描述banjo的使用。基本上就是,編寫一個簡單的協議,然後描述對應它的驅動程序。另一個在協議上綁定並使用該協議的驅動程序。如果有意義的話,可以修改現有的驅動程序編寫教程,以獲得有關banjo使用的更多詳細信息。我認爲當前的驅動程序教程也專注於C的使用,而獲得C ++版本(使用ddktl)可能會帶來最大的價值。這個已經在我的工作隊列中,“使用ddktl教程”(C++ DDK wrappers)。
現在我們看見生成的I2C驅動代碼,讓我們來看一下怎麼使用它。
(原英文文檔待完成。。。)
參考:這裏主要列出內建關鍵字和原語類型。
屬性:例子中的protocol部分有兩個屬性[layout]和[async]。
Layout
目前有三個可選類型,
* `ddk-protocol`
* `ddk-interface`
* `ddk-callback`
在protoco的前一行就是layout屬性,代碼如下:
[19] [Layout = "ddk-protocol"]
[20] protocol I2c {
爲了理解layout類型怎麼工作,我們現在假設有兩個驅動A和B。A驅動產出一個設備,然後B連接到該設備上,使得B作爲A的子。如果B通過device_get_protocol()獲取DDK它的父協議,B會得到一個ddk協議,`ddk-protocol`。`ddk-protocol`就是一組回調的集合,由父設備提供給子設備。
其中一個協議功能可以是註冊一個“反向協議”,由此子進程爲父進程提供一組回調來代替。這就是ddk接口,`ddk-interface`。
從生成的代碼來看,`ddk-protocol`和`ddk-interface`看起來大致相同,除了名字看上去有細微的差異。(`ddk-protocol`會自動將字符"protocol"附加到生成的結構體或者類字符的後面,`ddk-interface`卻不會這麼做)。
`ddk-callback`是對`ddk-interface`進行了稍微優化,用於當`ddk-interface`只有一個函數接口的時候。並不會生成兩個結構體,如下:
struct interface {
void* ctx;
inteface_function_ptr_table* callbacks;
};
struct interface_function_ptr_table {
void (*one_function)(...);
}
`ddk-callback`只會生成一個帶有函數指針成員的結構體,如下:
struct callback {
void* ctx;
void (*one_function)(...);
};
- 異步屬性
在 有protocol的一行,我們可以看到另一種屬性:異步屬性。
[20] protocl I2c {
... /// comments (removed)
[27] [Async]
異步屬性是一種,使協議消息不同步的方式。它會自動生成一個回調類型,其中輸出參數是回調的輸入。原始方法不會在其簽名中指定任何輸出參數。回想一下上面的例子,我們有一個`Transact`方法。
[28] Transact(vector<I2cOp> op) -> (zx.status status, vector<I2cOp> op);
當和`[Async]`屬性一起使用時,意味着,我們希望banjo調用一個回調函數,以便我們處理輸出數據(如上面代碼的`vector<I2cOp>`參數,表示來自I2C總線的數據)。
- 它怎麼工作的
我們通過第一個參數`vector<I2cOp>`發送數據到I2C總線。一段時間後,I2C總線可能會發出數據作爲我們請求的迴應。由於我們設置了異步屬性,banjo產生一個帶有回調函數作爲輸入參數的函數。
C語言版本中,`i2c.h`頭文件中這兩行比較重要:
[19] typedef void (*i2c_transact_callback)(void* ctx, zx_status_t status, const i2c_op_t* op_list, size_t op_count);
[36] void (*transact)(void* ctx, const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie);
C++版本中,我們涉及到兩處回調函數:
[083] static void I2cTransact(void* ctx, const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie) {
[084] static_cast<D*>(ctx)->I2cTransact(op_list, op_count, callback, cookie);
[085] }
和
[134] void Transact(const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie) const {
[135] ops_->transact(ctx_, op_list, op_count, callback, cookie);
[136] }
注意C++和C爲什麼如此相似,因爲生成的代碼將c頭文件作爲c++的部分頭文件。
Transaction回調函數有如下幾個參數:
Argument | Meaning
-----------|----------------------------------------
`ctx` | the cookie
`status` | status of the asynchronous response (provided by callee)
`op_list` | the data from the transfer
`op_count` | the number of elements in the transfer
這裏和我們上面提到的`ddk-callback` `[Layout]`屬性有什麼不同呢?
第一、這裏沒有包含callback和cookie成員的結構體,他們被當作內聯參數傳遞進去了。
第二、提供的回調時一次性使用功能。
就是說,它能且只能被調用一次它所提供的協議方法。
相比之下,`ddk-callback`的方法是一種註冊一次,調用多次的函數類型(類似於`ddk-interface`和`ddk-protocol`)。因此,`ddk-callback` 和 `ddk-interface`結構體,通常成對調用register()和unregister(),告訴父設備什麼時候應該調用回調函數。
另外`[Async]`需要提醒的是,回調必須通過協議方法調用,並且需要提供同行的cookie。不這樣做,可能會導致未定義的行爲(比如內存泄漏、死鎖、超時或者崩潰)。
雖然不是目前的情況,C ++和未來的語言綁定(如Rust)將在生成的代碼中提供基於“未來”/“承諾”樣式的API這些回調是爲了防止錯誤。還有一個關於“[異步]”的警告; `[Async]`屬性僅適用於緊隨其後的方法; 沒有任何其他方法。