一、前言
我們平常在寫代碼的時候,特別是在制造輪子的時候(為別人提供庫文件),會遇到各種不同的需求場景:
- 有些人需要在 Linux 系統下使用,有些人需要在 Windows 系統下使用;
- 有些人使用 C 語言開發,有些人使用 C++ 來開發;
- 有些人使用動態庫,有些人使用靜態庫;
特別是在 Windows 系統中,庫文件中導出的函數需要使用 _declspec(dllexport) 來聲明函數,而使用者在導入的時候,需要使用 _declspec(dllimport) 來聲明函數,甚是麻煩!
這篇短文分享一個頭文件,利用這個頭文件,再加上幾個編譯期間傳遞的宏,就可以完美的處理剛才所說的各種需求。
二、頭文件
先直接上代碼,可以先試著分析一下,后面我們再逐一分析不同的使用場景。
這個頭文件的主要目的,就是定義一個宏:MY_API,然后把這個宏添加在庫文件中每一個需要導出的函數或者類的聲明中即可。例如:
- void MY_API do_work();
下面是頭文件:
- _Pragma("once")
- #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
- #define MY_WIN32
- #elif defined(linux) || defined(__linux) || defined(__linux__)
- #define MY_LINUX
- #endif
- #if defined(MY_WIN32)
- #ifdef MY_API_STATIC
- #ifdef __cplusplus
- #define MY_API extern "C"
- #else
- #define MY_API
- #endif
- #else
- #ifdef MY_API_EXPORTS
- #ifdef __cplusplus
- #define MY_API extern "C" __declspec(dllexport)
- #else
- #define MY_API __declspec(dllexport)
- #endif
- #else
- #ifdef __cplusplus
- #define MY_API extern "C" __declspec(dllimport)
- #else
- #define MY_API __declspec(dllimport)
- #endif
- #endif
- #endif
- #elif defined(MY_LINUX)
- #ifdef __cplusplus
- #define MY_API extern "C"
- #else
- #define MY_API
- #endif
- #endif
三、預定義的宏
假設需要寫一個庫文件,提供給別人使用。定義了上面這個頭文件之后,其他的文件中都要include 這個頭文件。
1. 平臺宏定義
不同的平臺預定義了相應的宏定義,例如:
- Windows 平臺:WIN32, _WIN32, WIN32;
- Linux 平臺:linux, __linux, linux;
在一個確定的平臺上,這些宏不一定全部定義,很可能只有其中的某一個宏是被定義的。
為了統一性,我們在頭文件的剛開始部分,把這些可能的宏統一起來,定義我們出我們自己的平臺宏定義:MY_WIN32 或者是 MY_LINUX,后面需要區分不同的平臺時,就用這個自己定義的平臺宏。
當然,還可以繼續擴充出其他平臺,例如:MY_MAC, MY_ARM 等等。
2. 編譯器宏定義
如果在寫庫代碼的時候,使用的是 C++,而使用者使用的是 C 語言,那么就需要對庫函數進行extern “C” 聲明,讓編譯器不要對函數的名稱進行改寫。
編譯器 g++ 預定義了宏 __cplusplus,因此,在頭文件中,就利用了這個宏,在 MY_API 中添加 extern "C" 聲明。
四、Windows 平臺場景分析
1. 編譯生成庫文件
(1) 生成靜態庫
在靜態庫中,不需要 __declspec(dllexport/dllimport) 的聲明,因此只需要區分編譯器即可(gcc or g++),在編譯選項中定義宏 MY_API_STATIC,即可得到最終的 MY_API 為:
- gcc 編譯器:#define MY_API
- g++ 編譯器:#define MY_API extern "C"
(2) 生成動態庫
在編譯選項中,定義宏 MY_API_EXPORTS,這樣最終得到的 MY_API 就會變成:
- gcc 編譯器:#define MY_API __declspec(dllexport)
- g++ 編譯器:#define MY_API extern "C" __declspec(dllexport)
2. 使用庫
在使用庫的應用程序中,也需要在代碼中 include 這個頭文件,然后加上編譯選項中定義的各種宏,來生成對應的 MY_API 宏定義。
(1) 使用靜態庫
需要在編譯選項中定義 MY_API_STATIC,即可得到最終的 MY_API 為:
- gcc 編譯器:#define MY_API
- g++ 編譯器:#define MY_API extern "C"
(2) 使用動態庫
在編譯選項中不需要任何宏定義,即可得到最終的 MY_API 為:
- gcc 編譯器:#define MY_API extern "C" __declspec(dllimport)
- g++ 編譯器:#define MY_API __declspec(dllimport)
這樣就相當于聲明導入庫函數了。
五、Linux 平臺場景分析
Linux 平臺下就簡單多了,只需要注意編譯器的問題,而沒有導出和導入之分。
原文地址:https://mp.weixin.qq.com/s/N6Ome9vs9OEocqdWv-MfEw