嵌入式編程模塊化—— 君子協(xié)定
【說在前面的話】
將模塊視作一個(gè)黑盒子:模塊的設(shè)計(jì)者不用向外透露黑盒子的實(shí)現(xiàn)細(xì)節(jié);同時(shí)模塊的使用者也無法看到黑盒子的內(nèi)部。
模塊的設(shè)計(jì)者和模塊的使用者完全通過“接口”來進(jìn)行約定和溝通。這里所有的接口約定都是通過接口頭文件來進(jìn)行描述和傳遞的。
接口(及接口頭文件)遵循“最小信息公開原則”,即,任何跟使用模塊所提供的服務(wù)無關(guān)的、或者非必要的(可有可無)信息都應(yīng)該從接口頭文件中刪除。
如何隱藏模塊的實(shí)現(xiàn),或者說隱藏源代碼;
接口頭文件中數(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)體的成員。
【什么是掩碼結(jié)構(gòu)體】
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;};
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);externbool 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);

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)體。
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 么?
????typedef?__name?__name;????????????struct?__????????__VA_ARGS__??????????????????????????????\????}; \????struct?__name { \????????uint8_t?chMask[sizeof(struct?____attribute__((aligned( \__alignof__(struct __))); \????};????/*?這只是一個(gè)為未來預(yù)留的語法糖?*/????????struct?__???? (struct?__
借助上述宏,我們可以將接口頭文件 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)體類型:
......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 fullreturn false;}...}
【如何使用PLOOC來簡化開發(fā)】
從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.h 并在靠近結(jié)構(gòu)體定義的地方其中添加以下內(nèi)容:
/*! \NOTE: Make sure #include "plooc_class.h" is close to the class definition?*/???
__<模塊名稱>_CLASS_IMPLEMENT__<模塊名稱>_CLASS_INHERIT__....../*! \NOTE: Make sure #include "plooc_class.h" is close to the class definition*/.../* 頭文件的尾部 *//*! \note it is very important to undef those macros */
在 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 pointeruint16_t hwTail; //!< tail pointeruint16_t hwCount; //!< byte count))end_def_class(byte_queue_t) /* do not remove this for forward compatibility *///! @}
打開 byte_queue.c,在文件的最開始通過定義宏 __BYTE_QUEUE_CLASS_IMPLEMENT 來標(biāo)記自己“類主人”的身份,當(dāng)然,別忘記包含自己的接口頭文件:
在 byte_queue.c 中,如果某個(gè)函數(shù)(類的方法)試圖訪問類的成員,則應(yīng)該首先借助 class_internal() 來“脫下馬甲”。方法跟前文一樣,這里就不再贅述。
【后記】
掃描下方微信,加作者微信進(jìn)技術(shù)交流群,請(qǐng)先自我介紹喔。 推薦閱讀:
嵌入式編程專輯 Linux 學(xué)習(xí)專輯 C/C++編程專輯 Qt進(jìn)階學(xué)習(xí)專輯
如果你喜歡我的思維,歡迎訂閱 裸機(jī)思維

