跨平臺程式碼三種組織方式詳解
導讀 | 我們在原始碼中也會遇到一些跨平臺的問題。不同的功能,在不同的平臺下,實現方式是不一樣的,如何對這些平臺相關的程式碼進行組織呢?這篇文章就來聊聊這個問題。 |
在上一篇文章中,分享了一個跨平臺的標頭檔案是長成什麼樣子的,這個標頭檔案對於 windows 平臺下更有意義一些,因為要處理庫函式的匯入和匯出宣告(dllexport、dllimport)。
其實,可以在這個標頭檔案的基礎上繼續擴充,以達到更細粒度的控制。例如:對編譯器的判斷、對編譯器版本的判斷等等。
同樣的,我們在原始碼中也會遇到一些跨平臺的問題。不同的功能,在不同的平臺下,實現方式是不一樣的,如何對這些平臺相關的程式碼進行組織呢?這篇文章就來聊聊這個問題。
PS: 文末提供了一個簡單的、跨平臺構建程式碼示例。
假設我們寫一個庫,需要實現一個函式:獲取系統時間戳。作為實現庫的作者,你決定提供下面的 API 函式:
t_time.h: 宣告介面函式(t_get_timestamp); t_time.c:實現介面函式;
下面的任務就是在函式實現中,透過不同下的 C 庫或系統呼叫,來計算系統當前的時間戳。
在 平臺下,可以透過下面這段程式碼實現:
struct timeval tv; gettimeofday(&tv, null); return tv.tv_sec * 1000 + tv.tv_usec / 1000;
在 Windows 平臺下,可以透過下面這段程式碼實現:
struct timeb tp; ftime(&tp); return tp.time *1000 + tp.millitm;
那麼問題來了:怎麼把這兩段平臺相關的程式碼組織在一起?下面就介紹 3 種不同的組織方式,沒有優劣之分,每個人都有不同的習慣,選擇適合自己和團隊的方式就行。
此外,這個示例中只有 1 個函式,而且比較短小。如果這種跨平臺的函式很多、而且都很長,也許你的選擇又不一樣了。
直接在介面函式中,透過平臺宏定義來區分不同平臺。
平臺宏定義(T_LINUX, T_WINDOWS),是在上一篇文章中介紹的,透過作業系統、編譯器來判斷當前的平臺是什麼,然後定義出統一的平臺宏定義為我們自己所用:
程式碼組織方式如下:
int64 t_get_timestamp() { int64 ts = -1; #if defined(T_LINUX) struct timeval tv; gettimeofday(&tv, null); ts = tv.tv_sec * 1000 + tv.tv_usec / 1000; #elif defined(T_WINDOWS) struct timeb tp; ftime(&tp); ts = tp.time; ts = ts *1000 + tp.millitm; #endif return ts; }
這樣的方式,把所有平臺程式碼全部放在 API 函式中了,透過平臺宏定義進行條件編譯,因為程式碼比較短小,看起來還不錯。
把不同平臺的實現程式碼放在獨立的檔案中,然後透過 #include 預處理符號,在 API 函式中,把平臺相關的程式碼引入進來。
也就是再增加 2 個檔案:
t_time_linux.c:存放 Linux 平臺下的程式碼實現; t_time_windows.c:存放 Windows 平臺下的程式碼實現; (1) t_time_linux.c #include "t_time.h" #includeint64 t_get_timestamp() { int64 ts = -1; struct timeval tv; gettimeofday(&tv, null); ts = tv.tv_sec * 1000 + tv.tv_usec / 1000; return ts; }
(2) t_time_windows.c
#include "t_time.h" #include#includeint64 t_get_timestamp() { int64 ts = -1; struct timeb tp; ftime(&tp); ts = tp.time; ts = ts *1000 + tp.millitm; return ts; }
(3) t_time.c
這個檔案不做任何事情,僅僅是 include 其他的程式碼。
#include "t_time.h" #if defined(T_LINUX) #include#elif defined(T_WINDOWS) #include#else int64 t_get_timestamp() { return -1; } #endif
有些人比較反感這樣的組織方式,一般都是 include 一個 .h 標頭檔案,而這裡透過平臺宏定義,include 不同的 .c 原始檔,感覺怪怪的?!
其實,也有一些開源庫是這麼幹的,比如下面:
在上面方案2中,是在原始碼中填入不同平臺的實現程式碼。
其實可以換一種思路,既然已經根據平臺的不同、放在不同的檔案中了,那麼可以讓不同的原始檔加入到編譯過程中就可以了。
測試程式碼是使用 cmake 工具來構建的,因此可以編輯 CMakelists.txt 檔案,來控制參與編譯的原始檔。
CMakelists.txt 檔案部分內容
# 設定平臺變數 if (CMAKE_SYSTEM_NAME MATCHES "Linux") set(PLATFORM linux) elseif (CMAKE_SYSTEM_NAME MATCHES "Windows") set(PLATFORM windows) endif()
# 根據平臺變數,來編譯不同的原始檔
set(LIBSRC t_time_${PLATFORM}.c)
這樣的組織方式,感覺程式碼更“乾淨”一些。同樣的,我們也可以看到一些開源庫也是這麼做的:
為了文章的篇幅,以上只是貼了程式碼的片段。
我寫了一個最簡單的 demo,使用 cmake 來構建跨平臺的動態庫、靜態庫、可執行程式。寫這個 demo 的目的,主要是作為一個外殼,來測試一些寫文章時的程式碼。
在 Linux 平臺下,透過 cmake 指令手動編譯;在 Windows 平臺下,可以透過 CLion 整合開發環境直接編譯、執行,也可以透過 cmake 工具直接生成 VS2017/2019 解決方案。
已經把這個 demo 放在 gitee 倉庫中了
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2769691/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 程式碼模型組織方式模型
- 跨平臺專案組織——Codeblocks & VCBloC
- 「JavaScript」四種跨域方式詳解JavaScript跨域
- 六種組織CSS的方式CSS
- EF三種程式設計方式圖文詳解程式設計
- 實現 Java 平臺的三種方式(轉)Java
- 程式的定義、組成、組織方式、特徵特徵
- 組織css程式碼CSS
- mysql備份的三種方式詳解MySql
- LR低程式碼快速開發平臺 高效調整企業組織架構架構
- Go包-程式碼組織者Go
- 小程式跨平臺開發解決方案探索
- 短視訊平臺原始碼,Spring配置資料來源的三種方式原始碼Spring
- jProcesses:使用Java獲取跨平臺程式的詳細資訊Java
- 如何組織大型 Rust 程式碼庫Rust
- Sublime Text——高效的跨平臺程式碼編輯器
- Google Inbox 是如何跨平臺重用程式碼的?Go
- 各種字元編碼方式詳解及由來字元
- RMAN同位元組序跨平臺跨版本遷移資料庫資料庫
- 四種XML解析方式詳解XML
- 直播平臺原始碼,JavaScript 的四種除錯輸出方式原始碼JavaScript除錯
- 低程式碼平臺選型(三)國產化
- 位元組碼詳解
- 程式間的五種通訊方式介紹-詳解
- 跨平臺開發,各種巨集的定義
- 前端開發入門到實戰:六種組織CSS的方式前端CSS
- RMAN同位元組序跨平臺跨版本遷移資料庫(一)資料庫
- RMAN同位元組序跨平臺跨版本遷移資料庫(二)資料庫
- RMAN跨小版本跨平臺與位元組序傳輸表空間
- rust跨平臺Rust
- 低程式碼平臺建立數字應用程式的高效方式
- 相關前臺跨域的解決方式跨域
- 短視訊平臺原始碼,分享時生成二維碼的兩種方式原始碼
- Jmeter系列(45)- 詳解 Jmeter 跨執行緒組取引數值的方法,免程式碼!JMeter執行緒
- 乾貨 | 蘇寧影片雲跨平臺播放器開發方案詳解播放器
- (譯)詳解在React中跨元件分發狀態的三種方法React元件
- JavaScript 各種遍歷方式詳解JavaScript
- objective-C 的程式碼檔案組織Object