1. 分層設計
隔離平台相關的代碼, 就像可測試性一樣, 可移植性也要從設計抓起。一般來說, 最上
層和最下層都不具有良好的可移植性:
1). 最上層是 GUI, 大多數 GUI 都不是跨平台的, 如: Win32 SDK 和 MFC
2). 最下層是操作系統 API, 大部分操作系統 API 都是專用的
如果這兩層的代碼散布在整個軟件中, 那麼這個軟件的可植性將非常的差, 這是不言自
明的。那麼如何避免這種情況呢? 當然是分層設計了:
1). 最底層采用 Adapter 模式, 把不同操作系統的 API 封裝成一套統一的接口(如:
KYLib 庫), 至於封裝成類還是封裝成函數, 要看實際情況而定。如果在開發第
一個平台時就采用 KYLib, 可以大大減少移植的工作量。
2). 最上層采用分離界面表現與內部邏輯代碼的模式, 把大部分代碼放到內部邏輯裡
面, 界面僅僅是顯示和接收輸入, 即使要換一套 GUI, 工作量也不大。這同時也
是提高可測試性的手段之一, 當然還有其它一些附加好處。所以即使你采用 QT
或者 GTK+ 等跨平台的 GUI 設計軟件界面, 分離界面表現與內部邏輯也是非常
有用的。
2. 注意平台的特性
a. 目錄分隔符: 在Windows下用 '\\', 在Linux下用 '/'。
b. 文本文件換行符: 在Windows下用 "\r\n", 在Linux下用 '\n'。
c. 在 Windows 中文件名不區分大小寫字母, 而在 Linux 中則區分大小寫字母。
d. 在 Windows 中線程可以 suspend 和 resume, 而在 Linux 中則不允許此操作。
e. 在 Windows 的動態庫中, 除非明確指明為 export 的函數外, 其它函數對外都是不
可見的。
f. 在 Linux 的共享庫中, 所有非 static 的全局變量和函數, 對外全部是可見的。這
要特別小心, 同名函數引起的問題, 讓你查上兩天也不為過。
g. 在 Linux 的共享庫中, 如果想綁定共享庫裡的全局符號(變量, 函數和類等等), 則
在鏈接共享庫的時候, 添加 gcc 選項 -Wl,-Bsymbolic 即可。
h. 在 Linux 的共享庫中, 如果共享庫存取主程序裡定義的全局符號, 鏈接主程序的時
候, 使用參數 -Wl,--export-dynamic 即可。
3. 最好不要使用編譯器特有的特性
a. 像在 VC 裡, 你要實現線程局部存儲, 在變量前加一個 __declspec( thread ) 就行
了, 然而盡管在 pthread 裡有類似的功能, 卻不能按這種方式實現, 所以無法移植
到 Linux 下。
b. 同樣 gcc 也有很多擴展, 是在 VC 或者其它編譯器裡所沒有的。如編譯成多線程安
全的選項 -pthread, 此選項在編譯源程序和鏈接時使用。
4. 數據類型差別
a. 在 VC 中64位整型是 __int64, 而在 Linux 中是 int64_t。
b. 在 VC 中函數指針默認情況下可以直接賦值給 void* 類型變量, 而在 Linux 中則不
允許直接賦值, 必須使用 (void*) 強制轉換。
c. 在 Windows 中的原子鎖相關函數 InterlockXXX 中的參數類型是 long*,
而在 Linux 中的原子鎖相關函數 InterlockXXX 需要用AT&T內嵌匯編實現。
5. 調用外部庫(靜態庫和動態庫)差異
a. 在 VC 中調用外部庫有 .lib 支持, 若是動態庫則直接通過 .lib 關聯。
b. 在 Linux 中調用靜態庫為 .a 文件, 庫之間的先後順序非常重要, 如 libKYLib.a
和 libkylin.a, 且 kylin 依賴 KYLib, 則在工程中加載庫的順序必須為: 先加載
libkylin.a, 再加載 libKYLib.a。
c. 在 Linux 中調用動態庫為 .so 文件, 如果有好幾個庫, 它們之間有一些依賴關系的
話, 例如 X 依賴 Y, 那麼你就要先加載那些被依賴的 Y, 然後加載 X。
d. 在 Linux 中混合調用靜態庫和動態庫, 如使用 libKYLib.a 和 librc32c.so, 且
librc32c.so 中使用了 libKYLib.a, 則在加載庫時必須先加載 libKYLib.a, 然
後再加載 librc32c.so。
6. 加載動態庫時查找路徑順序的差異
a. Windows 庫搜索路徑和順序
1). 應用程序目錄
2). 當前工作目錄
3). 系統目錄 (%systemroot%, %systemroot%\system 和 %systemroot%\system32),
如: C:\WINNT\, C:\WINNT\system, C:\WINNT\system32
4). 路徑變量 (系統的環境變量 Path)
b. Linux 庫搜索路徑和順序
1). 鏈接時指定的路徑, 如: -Wl,-rpath=./ 選項表示編譯時 ld 路徑
2). 環境變量 LD_LIBRARY_PATH 指明的路徑
3). /etc/ld.so.cache中的函數庫列表
4). /lib目錄, 然後/usr/lib
5). 當前工作目錄
7. 動態庫入口函數的差異
a. Windows 中有 DllMain 入口函數, 而 Linux 中則沒有。
b. Linux 中有特殊函數 _init 和 _fini, 主要是分別用來初始化函數庫和關閉的時候
做一些必要的處理, 我們可以把自己認為需要的代碼放到這兩個函數裡面, 它們分別
在函數庫被加載和釋放的時候被執行。具體說, 如果一個函數庫裡面有一個名字為
"_init" 的函數輸出, 那麼在第一次通過 dlopen() 函數打開這個函數庫, 或者只是
簡單的作為共享函數庫被打開的時候, _init 函數被自動調用執行。與之相對應的就
是 _fini 函數, 當一個程序調用 dlclose() 去釋放對這個函數庫的引用的時候, 如
果該函數庫的被引用計數器為 0 了, 或者這個函數庫是作為一般的共享函數庫被使
用而使用它的程序正常退出的時候, _fini就會被調用執行。
C語言定義它們的原型如下:
void _init(void);
void _fini(void);
當使用你自己的 _init 和 _fini 函數時, 會出現命名沖突, 就會得到一個
"multiple-definition" 的錯誤, 編譯器提示已經存在這個名字, 可以通過幾種方式
來解決:
1). 自定義 init 函數名字, 比如 myinit 用 -Wl, 選項給 ld 傳遞此名字:
gcc ... -Wl,-init=myinit
2). 當 GCC 編譯源程序時, 可以使用選項 -nostartfiles 來使共享庫不與系統
啟動文件一起編譯
gcc ... -nostartfiles
3). 使用上面的函數或 GCC 的 -nostartfiles 選項並不是很好的習慣, 因為這
可能會產生一些意外的結果。相反, 庫應該使用
__attribute__((constructor)) 和 __attribute__((destructor)) 函數屬
性來輸出它的構造函數和析構函數。如下所示:
void __attribute__((constructor)) x_init(void);
void __attribute__((destructor)) x_fini(void);
構造函數會在dlopen()返回前或庫被裝載時調用;
析構函數會在這樣幾種情況下被調用: dlclose() 返回前, 或 main() 返回
後, 或裝載庫過程中 exit() 被調用時。
c. Linux 中的初始化和釋放函數不建議使用。
作者:kyee