跨平臺程式碼三種組織方式詳解

大雄45發表於2021-04-25
導讀 我們在原始碼中也會遇到一些跨平臺的問題。不同的功能,在不同的平臺下,實現方式是不一樣的,如何對這些平臺相關的程式碼進行組織呢?這篇文章就來聊聊這個問題。

跨平臺程式碼三種組織方式詳解跨平臺程式碼三種組織方式詳解

一、緣起

在上一篇文章中,分享了一個跨平臺的標頭檔案是長成什麼樣子的,這個標頭檔案對於 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 個函式,而且比較短小。如果這種跨平臺的函式很多、而且都很長,也許你的選擇又不一樣了。

三、三個解決方案
方案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 函式中了,透過平臺宏定義進行條件編譯,因為程式碼比較短小,看起來還不錯。

方案2

把不同平臺的實現程式碼放在獨立的檔案中,然後透過 #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 原始檔,感覺怪怪的?!

其實,也有一些開源庫是這麼幹的,比如下面:
跨平臺程式碼三種組織方式詳解跨平臺程式碼三種組織方式詳解

方案3

在上面方案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)

這樣的組織方式,感覺程式碼更“乾淨”一些。同樣的,我們也可以看到一些開源庫也是這麼做的:
跨平臺程式碼三種組織方式詳解跨平臺程式碼三種組織方式詳解
跨平臺程式碼三種組織方式詳解跨平臺程式碼三種組織方式詳解

四、One More Thing

為了文章的篇幅,以上只是貼了程式碼的片段。

我寫了一個最簡單的 demo,使用 cmake 來構建跨平臺的動態庫、靜態庫、可執行程式。寫這個 demo 的目的,主要是作為一個外殼,來測試一些寫文章時的程式碼。

在 Linux 平臺下,透過 cmake 指令手動編譯;在 Windows 平臺下,可以透過 CLion 整合開發環境直接編譯、執行,也可以透過 cmake 工具直接生成 VS2017/2019 解決方案。

已經把這個 demo 放在 gitee 倉庫中了


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2769691/,如需轉載,請註明出處,否則將追究法律責任。

相關文章