<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          嵌入式編程模塊化—— 君子協(xié)定

          共 7987字,需瀏覽 16分鐘

           ·

          2020-12-06 21:48

          【說在前面的話】


          在本系列的前一篇文章《嵌入式編程模塊化——6張圖來解析實(shí)用型Service模型》中,我們介紹了一種模塊化封裝的模型——Service模型。該模型的設(shè)計(jì)理念實(shí)際上服務(wù)于一個(gè)叫做“黑盒子哲學(xué)”的設(shè)計(jì)思維,其核心思想是:
          • 將模塊視作一個(gè)黑盒子:模塊的設(shè)計(jì)者不用向外透露黑盒子的實(shí)現(xiàn)細(xì)節(jié);同時(shí)模塊的使用者也無法看到黑盒子的內(nèi)部。

          • 模塊的設(shè)計(jì)者和模塊的使用者完全通過“接口”來進(jìn)行約定和溝通。這里所有的接口約定都是通過接口頭文件來進(jìn)行描述和傳遞的。

          • 接口(及接口頭文件)遵循“最小信息公開原則”,即,任何跟使用模塊所提供的服務(wù)無關(guān)的、或者非必要(可有可無)信息都應(yīng)該從接口頭文件中刪除。


          實(shí)踐中,要想實(shí)現(xiàn)黑盒子,我們實(shí)際上要完成兩大任務(wù):
          1. 如何隱藏模塊的實(shí)現(xiàn),或者說隱藏源代碼;

          2. 接口頭文件中數(shù)據(jù)結(jié)構(gòu)的保護(hù),或者說如何阻止用戶繞開模塊所提供的API而直接訪問關(guān)鍵結(jié)構(gòu)體的內(nèi)部(私有)成員;


          對(duì)于第一條來說,我們只需要把模塊編譯成library,連同接口頭文件一起提供給客戶使用就可以做到;而對(duì)于第二條要想實(shí)現(xiàn)起來卻并非那么簡單——雖然我們常常說C語言可以通過結(jié)構(gòu)體來模擬類的概念,但它卻無法像C++的類那樣提供對(duì)私有(private)和受保護(hù)(protected)成員的隱藏。換句話說,在實(shí)踐“最小信息公開原則”的時(shí)候,如果用戶調(diào)用服務(wù)的時(shí)候,確實(shí)需要用到結(jié)構(gòu)體(這個(gè)結(jié)構(gòu)體是最小信息),如何防止結(jié)構(gòu)體的定義信息被“非法使用”,就成了一個(gè)切實(shí)的難題。


          為了讓后續(xù)的討論更為清晰,我們不妨具體的定義一下我們的任務(wù):

          • 只允許用戶使用結(jié)構(gòu)體的大小對(duì)齊信息——這樣用戶可以自由的定義變量,或是通過malloc這樣的函數(shù)進(jìn)行動(dòng)態(tài)分配;
          • 以某種“通過實(shí)際手段強(qiáng)制了的君子協(xié)定”的形式——僅在語法層面——阻止用戶直接訪問結(jié)構(gòu)體的成員。

          要想同時(shí)做到以上兩點(diǎn),離不開今天索要介紹的主角:掩碼結(jié)構(gòu)體(Masked Structure)。

          【什么是掩碼結(jié)構(gòu)體】


          要想理解掩碼結(jié)構(gòu)體,拋開復(fù)雜和抽象的文字描述,我們不妨來看一個(gè)具體的例子:假設(shè)我們做了一個(gè)字節(jié)隊(duì)列的模塊,其中最核心的結(jié)構(gòu)體 byte_queue_t 的定義如下:
          typedef struct byte_queue_t byte_queue_t;struct byte_queue_t {????uint8_t *pchBuffer;????uint16_t hwSize;????????uint16_t hwHead;????uint16_t?hwTail;????uint16_t hwCount;};
          針對(duì)這一結(jié)構(gòu)體(或者叫類)我們提供一系列API(或者叫類的方法),比如:
          typedef struct byte_queue_cfg_t {    uint8_t *pchBuffer;????uint16_t?hwSize;???????????????????????} byte_queue_cfg_t;
          externbyte_queue_t * byte_queue_init(byte_queue_t *ptObj, byte_queue_cfg_t *ptCFG);
          extern bool byte_queue_enqueue(byte_queue_t *ptObj, uint8_t chByte);
          externbool byte_queue_dequeue(byte_queue_t *ptObj, uint8_t *pchByte);
          externuint_fast16_t byte_queue_count(byte_queue_t *ptObj);
          為了保證模塊的正常工作,防止運(yùn)行期間,用戶為了自身的便利,直接”外科手術(shù)式的“訪問 byte_queue_t 的成員導(dǎo)致不必要的問題(比如用戶說:我知道你遵循的是最小信息公開原則,也就是說,只要你放了結(jié)構(gòu)體在接口頭文件里,我當(dāng)然理解為我可以任意使用咯?),我們想將整個(gè) byte_queue_t 都保護(hù)起來——這就好比,我們?cè)噲D引入一個(gè)“蒙版”,遮住結(jié)構(gòu)體的成員信息然后在客戶的耳邊念起魔咒:
          你什么都看不到,你看到了也沒法用……
          你什么都看不到,你看到了也沒法用……
          你什么都看不到,你看到了也沒法用……
          ...

          要想實(shí)現(xiàn)這樣的“蒙版效果”其實(shí)并不困難,只需要知道要屏蔽的部分實(shí)際占用memory的大小,再根據(jù)這一大小來定義數(shù)組即可,因此,我們可以修改對(duì)應(yīng)的定義為:
          typedef struct byte_queue_t byte_queue_t;
          struct?__byte_queue_t?{ uint8_t *pchBuffer; uint16_t hwSize; uint16_t hwHead; uint16_t hwTail; uint16_t hwCount;};
          struct byte_queue_t {????uint8_t?chMask[sizeof(struct __byte_queue_t)];};

          這里,我們實(shí)際上是給原來的類型重命名為__byte_queue_t,并建立了一個(gè)內(nèi)部只使用數(shù)組來“濫竽充數(shù)”的替身——也就是我們所說的掩碼結(jié)構(gòu)體。

          如果你看過我之前的文章《漫談C變量——對(duì)齊(3)》,你會(huì)注意到,上述替身實(shí)際上丟失了結(jié)構(gòu)體 __byte_queue_t 的對(duì)齊信息——容易注意到 struct __byte_queue_t 的結(jié)構(gòu)體整體是對(duì)齊到 4 字節(jié)的,而掩碼結(jié)構(gòu)體中數(shù)組chMask本身是對(duì)齊到字節(jié)的——這會(huì)導(dǎo)致當(dāng)用戶使用掩碼結(jié)構(gòu)體來定義變量時(shí),由編譯器分配的空間可能無法滿足原結(jié)構(gòu)體對(duì)對(duì)齊的要求,造成非對(duì)齊訪問——輕則性能下降,重則hardfault。
          要解決這一問題也并不復(fù)雜,只需要借助GCC擴(kuò)展的運(yùn)算符 __alignof__() 提取目標(biāo)類型的對(duì)齊信息,再使用?__attribute__((aligned())) 來設(shè)置掩碼數(shù)組的對(duì)齊要求就可以了:
          typedef struct byte_queue_t byte_queue_t;
          struct __byte_queue_t { uint8_t *pchBuffer; uint16_t hwSize; uint16_t hwHead; uint16_t hwTail; uint16_t hwCount;};
          struct byte_queue_t { uint8_t chMask[sizeof(struct __byte_queue_t)] ????????__attribute__((aligned(???????? __alignof__(struct __byte_queue_t)??????? )));};

          至此,掩碼結(jié)構(gòu)體 byte_queue_t 擁有了和原本的結(jié)構(gòu)體 struct __byte_queue_t 一樣的尺寸和對(duì)齊;同時(shí)還在“語法”層面阻止了用戶直接訪問結(jié)構(gòu)體成員的可能(當(dāng)然,這也只能防君子不防小人),我們?cè)驹O(shè)立的兩個(gè)目標(biāo)都已成功達(dá)成。然而,聰明的你會(huì)在腦海里浮現(xiàn)出一個(gè)疑問——要想掩碼結(jié)構(gòu)體能正常工作,上述信息都必須放置到接口頭文件中,難道用戶是傻子,看不到結(jié)構(gòu)體 __byte_queue_t 么?


          借助宏的力量,我們可以成功的隱藏住 struct __byte_queue_t 的存在。

          下面的宏只是為了演示一種簡單的實(shí)現(xiàn)方法,暫時(shí)的打消你的疑慮,而實(shí)際在后面我們將要介紹的PLOOC模板中所使用的技法則更為復(fù)雜。由于本文只是著重于實(shí)際工程實(shí)踐中如何簡單的應(yīng)用掩碼結(jié)構(gòu)體,而不在于介紹復(fù)雜的宏技巧,因此我們將不在討論 PLOOC的實(shí)現(xiàn)細(xì)節(jié)。


          #define?declare_class(__name)     \????typedef?__name?__name;????????
          #define?def_class(__name, ...) \????struct?__##__name?{?????????? \????????__VA_ARGS__??????????????????????????????\????}; \????struct?__name { \????????uint8_t?chMask[sizeof(struct?__##__name)]\ __attribute__((aligned( \ __alignof__(struct __##__name) \ ))); \????};????/*?這只是一個(gè)為未來預(yù)留的語法糖?*/#define?end_def_class(...)????#define?class_internal(__obj_ptr,?__ptr, __type) \????struct?__##__type?*?__ptr?=??????????????????\???? (struct?__##__type?*)(__obj_ptr)

          借助上述宏,我們可以將接口頭文件 byte_queue.h 中代碼簡化為:

          ...declare_class(byte_queue_t)
          def_class(byte_queue_t, uint8_t *pchBuffer; uint16_t hwSize; uint16_t hwHead; uint16_t hwTail; uint16_t hwCount;)
          end_def_class(byte_queue_t)...

          而模塊源代碼中,則可以使用 class_internal() 來獲取原本的結(jié)構(gòu)體類型:

          ...#include?"./byte_queue.h"...
          #undef this#define?this (*ptThis)
          bool byte_queue_enqueue(byte_queue_t *ptObj, uint8_t chByte){ /* initialise "this" (i.e. ptThis) to access class members */ class_internal(ptObj, ptThis, byte_queue_t); ... if ( (this.hwHead == this.hwTail) && (0 != this.hwCount)) { //! queue is full return false; } ...}


          【如何使用PLOOC來簡化開發(fā)】


          PLOOCProtected Low-overhead?Object-Oriented programming with ANSI-C?的英文縮寫,意為:為(類)提供保護(hù)的、低開銷的、面向?qū)ο驝語言開發(fā)。它是我在 Github 上的一個(gè)開源項(xiàng)目(https://github.com/GorgonMeducer/PLOOC)。PLOOC 是目前已知唯一使用掩碼結(jié)構(gòu)體對(duì)私有(private)和受保護(hù)(protected)的成員提供隱藏的OOPC模板;除此以外,通過幾近于0的額外資源消耗來實(shí)現(xiàn)面向?qū)ο蠓庋b特性,也是PLOOC的一大賣點(diǎn)。

          雖然PLOOC自帶的 MDK 例子工程演示了常見的面向?qū)ο筇匦裕幱跁r(shí)間問題,仍然沒有來得及提供一份簡單直接的手把手使用教程。這里我們?nèi)匀灰?byte_queue_t 為例,為大家介紹一下如何在自己的工程中部署 PLOOC,并應(yīng)用到 service模型中。

          準(zhǔn)備階段
          • 從Github上下載最新的 release 版本。


          • 解壓縮后重命名目錄為 PLOOC,并復(fù)制到你的目標(biāo)工程中


          • 在你的工程中添加對(duì)PLOOC目錄的引用



          • 在工程配置中打開對(duì) C99 的支持,如果可能,直接開啟 C11和GNU擴(kuò)展的支持:


          • 如果你使用的是 gcc, clang 或是 arm compiler 6,你還需要打開對(duì)微軟擴(kuò)展的支持(-fms-extensions)并屏蔽一些惱人且無害的 warning:

          -fms-extensions -Wno-microsoft-anon-tag -Wno-empty-body

          NOTE:如果你使用的是 arm compiler 6,在開啟微軟擴(kuò)展以后,還需要額外定義一個(gè)宏 _MSC_VER 來避免底層庫中的一些不必要的編譯錯(cuò)誤。


          至此,我們就完成了 PLOOC 在你工程中的部署。


          如何在模塊中部署
          仍以 byte_queue 模塊為例,假設(shè)你已經(jīng)根據(jù) service 模型構(gòu)建好了目錄結(jié)構(gòu):

          • 打開接口頭文件 byte_queue.h 并在靠近結(jié)構(gòu)體定義的地方其中添加以下內(nèi)容:

          /*! \NOTE: Make sure #include "plooc_class.h" is close to the class definition ?*/???#if     defined(__BYTE_QUEUE_CLASS_IMPLEMENT)#   define __PLOOC_CLASS_IMPLEMENT__#elif   defined(__BYTE_QUEUE_CLASS_INHERIT__)#   define __PLOOC_CLASS_INHERIT__#endif   
          #include?"plooc_class.h"
          這里,我們定義了兩個(gè)很重要的宏 __BYTE_QUEUE_CLASS_IMPLEMENT 和?__BYTE_QUEUE_CLASS_INHERIT__。容易看出,他們分別是根據(jù)?
          __<模塊名稱>_CLASS_IMPLEMENT
          和?
          __<模塊名稱>_CLASS_INHERIT__
          的形式改寫而成的。前者的作用是給 C 源代碼標(biāo)記“我是這個(gè)類的實(shí)現(xiàn),我是類的主人”的身份用的;后者的作用是給 C代碼標(biāo)記“我是派生類的實(shí)現(xiàn),我派生自基類”。具體使用方法,后面會(huì)具體介紹。

          需要特別強(qiáng)調(diào)的是,一定不要忘記在接口頭文件的尾部將這兩個(gè)宏都undef掉
          ...#ifndef __PLOOC_EXAMPLE_BYTE_QUEUE_H__#define __PLOOC_EXAMPLE_BYTE_QUEUE_H__...
          /*! \NOTE: Make sure #include "plooc_class.h" is close to the class definition */ #if defined(__BYTE_QUEUE_CLASS_IMPLEMENT)# define __PLOOC_CLASS_IMPLEMENT__#elif defined(__BYTE_QUEUE_CLASS_INHERIT__)# define __PLOOC_CLASS_INHERIT__#endif
          #include "plooc_class.h"...

          /* 頭文件的尾部 */
          /*! \note it is very important to undef those macros */#undef __BYTE_QUEUE_CLASS_INHERIT#undef __BYTE_QUEUE_CLASS_IMPLEMENT__
          #endif


          • 在 byte_queue.h 里定義目標(biāo)類:

          //! \name class byte_queue_t//! @{declare_class(byte_queue_t)
          def_class(byte_queue_t,
          private_member( uint8_t *pchBuffer; uint16_t hwSize; ) protected_member( uint16_t hwHead; //!< head pointer uint16_t hwTail; //!< tail pointer uint16_t hwCount; //!< byte count ))
          end_def_class(byte_queue_t) /* do not remove this for forward compatibility *///! @}
          值得注意的是,這里我們用 private_member()protected_member()?的形式規(guī)定了成員變量的屬性:其中private的成員是只有類的主人自己可見;而 protected的成員是類的主人以及派生類都可見。如果你想指定某些成員是公共可見的,則可以使用 public_member()。


          • 打開 byte_queue.c,在文件的最開始通過定義宏 __BYTE_QUEUE_CLASS_IMPLEMENT 來標(biāo)記自己“類主人”的身份,當(dāng)然,別忘記包含自己的接口頭文件:

          #define __BYTE_QUEUE_CLASS_IMPLEMENT
          #include?"./byte_queue.h"


          • 在 byte_queue.c 中,如果某個(gè)函數(shù)(類的方法)試圖訪問類的成員,則應(yīng)該首先借助 class_internal() 來“脫下馬甲”。方法跟前文一樣,這里就不再贅述。


          完整的例子在 PLOOC 的example目錄下:諸如派生類應(yīng)該如何處理函數(shù)重載應(yīng)該如何實(shí)現(xiàn)等等問題,大家可以打開MDK的例子工程后“細(xì)品”。


          【后記】


          掩碼結(jié)構(gòu)體是一種全新的方法,可以在語法層面上限制模塊的使用者對(duì)關(guān)鍵的結(jié)構(gòu)體(類)成員的訪問。相比大家熟悉的“不完全類型”,掩碼結(jié)構(gòu)體攜帶了足夠的信息(大小信息和對(duì)齊信息),從而允許模塊的使用者自由的定義變量或是動(dòng)態(tài)分配,這與“不完全類型”必須依賴動(dòng)態(tài)分配的缺點(diǎn)形成了鮮明的對(duì)比。
          曾幾何時(shí),掩碼結(jié)構(gòu)體還有“模塊的.c不能包含模塊的接口頭文件” 這樣的限定,在最新的PLOOC中,這一問題已經(jīng)得到了徹底的解決——再也不用擔(dān)心 ".c" 和 ".h" 中的類型描述不一致導(dǎo)致的運(yùn)行時(shí)錯(cuò)誤。

          最后,需要強(qiáng)調(diào)一下,對(duì) service 模型來說,掩碼結(jié)構(gòu)體,或者說PLOOC的使用只是“錦上添花”——并非必須。讀者完全可以根據(jù)自己的喜好來決定模塊的實(shí)現(xiàn)方式。如果你喜歡或者對(duì)PLOOC使用有什么建議,歡迎在 github上提交你的issue。


          掃描下方微信,加作者微信進(jìn)技術(shù)交流群,請(qǐng)先自我介紹喔。


          推薦閱讀:


          嵌入式編程專輯
          Linux 學(xué)習(xí)專輯
          C/C++編程專輯
          Qt進(jìn)階學(xué)習(xí)專輯

          如果你喜歡我的思維,歡迎訂閱 裸機(jī)思維

          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本福利影音网站 | 中文字幕午夜做爱 | 玩弄大荫蒂视频 | 性在线视频 | 黄色免费国产电影 |