Linux 平台相關代碼的 C++ 解決方案
Linux 平台相關代碼帶來的問題
目前市場上存在著許多不同的 Linux 平台(例如:RedHat, Ubuntu, Suse 等),各大廠商和社區都在針對自己支持的平台進行優化,為使用者帶來諸多方便的同時也對軟體研發人員在進行編碼時帶來不少問題:
(1)由於程序中不可避免的存在平台相關代碼(系統調用等),軟體研發人員為了保證自己的產品在各個 Linux 平台上運行順暢,一般都需要在源代碼中大量使用預編譯參數,這樣會大大降低程序的可讀性和可維護性。
(2)介面平台無關性的原則是研發人員必須遵循的準則。但是在處理平台相關代碼時如果處理不當,此原則很有可能被破壞,導致不良的編碼風格,影響代碼的擴展和維護。
本文將針對這兩個問題循序漸進依次提出解決方案。
通過設置預編譯選項來處理平台相關代碼
通過為每個平台設置相關的預編譯宏能夠解決 Linux 平台相關代碼的問題,實際情況下,很多軟體開發人員也樂於單獨使用這種方法來解決問題。
假設現有一動態庫 Results.so,SomeFunction() 是該庫的一個導出函數,該庫同時為 Rhel,Suse,Ubuntu 等三個平台的 Linux 上層程序服務。(後文例子均基於此例並予以擴展。)
清單 1. 設置預編譯選項示例代碼如下:
// Procedure.cpp
void SomeFunction(){ //Common code for all linux ...... ......#ifdef RHELSpecialCaseForRHEL();
#endif#ifdef SUSE SpecialCaseForSUSE();#endif#ifdef UBUNTU SpecialCaseForUBUNTU();#endif //Common code for all linux ............
#ifdef RHEL SpecialCase2ForRHEL();#endif#ifdef SUSE SpecialCase2ForSUSE();#endif#ifdef UBUNTU SpecialCase2ForUBUNTU();#endif//Common code for all linux
............}
開發人員可以通過設置 makefile 宏參數或者直接設置 gcc 參數來控制實際編譯內容。
例如:
gcc -D RHEL Procedure.cpp -o Result.so -lstdc++ // Use RHEL marco
SpecialCaseForRHEL(),SpecialCaseForSUSE(),SpecialCaseForUBUNTU() 分別在該庫 (Results.so) 的其他文件中予以實現。
帶來的問題
SomeFunction() 函數代碼冗餘,格式混亂。本例僅涉及三個預編譯選項,但實際情況中由於 Linux 版本眾多並且可能涉及操作系統位數的問題,增加對新系統的支持會導致預編譯選項不斷增多,造成 SomeFunction() 函數結構十分混亂。
新增其他平台相關介面(例如:增加 SpecialCase3ForRHEL(),SpecialCase3ForSUSE(),SpecialCase3ForUBUNTU),會成倍增加代碼中預編譯宏的數量。
破壞了介面平台無關性的原則。SpecialCaseForRHEL(),SpecialCaseForSUSE(),SpecialCaseForUBUNTU() 只是同一功能各個平台的不同實現,屬於封裝內容,不應該分開暴露給調用者。
可見,簡單利用預編譯宏來解決平台相關代碼產生的問題不是一個好的方法,並沒有解決本文開始提出的兩個問題。後文將通過三個方案依次解決這些問題。
解決方案 1:根據介面平台無關性原則進行優化
實質上,SpecialCaseForRHEL(),SpecialCaseForSUSE(),SpecialCaseForUBUNTU() 只是同一功能在不同平台上的實現,SpecialCase2ForRHEL(),SpecialCase2ForSUSE(),SpecialCase2ForUBUNTU() 亦如此。對於調用者,應該遵循介面平台無關性的原則,使用統一的介面進行調用,這樣才能簡化代碼,使代碼易於維護。
清單 2. 解決方案 1 示例代碼如下:
// Procedure.cpp
void SomeFunction(){ //Common code for all linux ...... ...... SpecialCase(); //Common code for all linux......
...... SpecialCase2(); //Common code for all linux ...... ......}void SpecialCase(){ //Common code for all linux......
......#ifdef RHEL SpecialCaseForRHEL();#endif#ifdef SUSE SpecialCaseForSUSE();#endif#ifdef UBUNTU SpecialCaseForUBUNTU();#endif
//Common code for all linux ...... ......}void Special2Case(){ //Common code for all linux ...... ......#ifdef RHEL
SpecialCase2ForRHEL();#endif#ifdef SUSE SpecialCase2ForSUSE();#endif#ifdef UBUNTU SpecialCase2ForUBUNTU();#endif //Common code for all linux ...... ......}
此方案的優點:
遵循了介面平台無關性原則,同樣的功能只提供一個介面,每個平台的實現屬於實現細節,封裝在介面內部。此方案提供了一定的封裝性,簡化了調用者的操作。
此方案的缺點:
預編譯宏泛濫的問題仍然沒有解決,每次新增功能函數,就會成倍增加預編譯宏的數量。同樣每次增加對已有功能新平台的支持,也會不斷增加預編譯宏的數量。
可見,此方案部分解決了本文開始提出的兩個問題中的一個,但仍有問題需要繼續解決。
解決方案 2: 通過分層對進行優化
換一個角度來思考,可以在二進位層面對平台相關代碼進行優化。通過對庫的結構進行分層來優化,為每個 Linux 平台提供單獨的實現庫,並且把調用端獨立提取出來。如下圖所示:
圖 1: 方案 2 的結構圖