在 CMakeLists.txt 中添加編譯動態(tài)庫選項

在 CMakeLists.txt 中添加編譯動態(tài)庫選項

在使用 CMake 構(gòu)建項目時牡属,一個常見的應(yīng)用就是使用 CMake 編譯一個庫文件了。而編譯成一個動態(tài)庫或者靜態(tài)庫又是編譯庫文件時經(jīng)常使用的一個選項驼鹅。本文介紹了如何在 CMake 中添加一個選項來控制是否將庫編譯為動態(tài)庫咐容,且該選項可以和 CMake 一樣跨平臺使用。

本文并不從動態(tài)庫和靜態(tài)庫的起源開始講起,因此有些預(yù)備知識需要你提前了解米奸。這些知識不需要你深刻的了解并實踐過,只需看過相關(guān)文章有所了解即可爽篷。這些預(yù)備知識包括:

  • C++ 基本編程知識悴晰。

  • 什么是動態(tài)庫。

  • 動態(tài)庫和靜態(tài)庫的區(qū)別逐工。

  • CMake 是干什么的铡溪,CMake 的基本知識。

上面的預(yù)備知識可以在網(wǎng)上很容易查閱得到泪喊,Static vs Dynamic Libraries 這篇英文文章介紹的也比較詳細(xì)棕硫,可以作為參考。

主要難點

關(guān)于在 CMake 中添加動態(tài)庫的選項袒啼,主要的難點就在于 windows 平臺上的改動較大哈扮。在 linux 平臺下纬纪,動態(tài)庫和靜態(tài)庫的源代碼是完全一樣的,只需要修改編譯參數(shù)即可滑肉。而 windows 平臺下包各,如果想把靜態(tài)庫變成動態(tài)庫的話,所有的源碼都需要改變靶庙!因此如果你之前寫了大量代碼而沒有考慮到 windows 平臺上編譯動態(tài)庫的話问畅,修改起來可能會是一個很大的工程。本文將以一個簡單的實例來介紹如何編寫一個可跨平臺的帶動靜態(tài)編譯參數(shù)的 CMake 項目六荒。示例代碼可以在 這里 查看护姆。

Windows 平臺下的動態(tài)庫導(dǎo)入導(dǎo)出

前面我們提到過,在 Windows 平臺中生成動態(tài)庫其源碼和靜態(tài)庫是不同的掏击。在 Windows 平臺中签则,我們導(dǎo)出動態(tài)庫時,除了會生成 .dll 動態(tài)庫之外還會生成一個 .lib 文件铐料。這個 .lib 文件和靜態(tài)庫的 .lib 文件不同,它里面并不保存代碼生成的二進(jìn)制文件豺旬,而是所有需要導(dǎo)出符號的符號表钠惩。因此這個 .lib 文件和編譯靜態(tài)庫生成的 .lib 文件相比會小很多。而這個導(dǎo)出的符號表是需要我們在源碼中進(jìn)行指定的族阅。如果我們希望將將一個符號(symbol)導(dǎo)出(這里的符號可以指類篓跛、函數(shù)等各種類型),需要在其前面加上 __declspec(dllexport) 標(biāo)志坦刀。這樣這個符號的相關(guān)信息就會導(dǎo)出的 .lib 中的符號表中了愧沟。如果我們的源碼中沒有任何 __declspec(dllexport) 的話,我們依然可以成功的編譯出動態(tài)庫鲤遥,但是并不會生成保存符號表的.lib 文件沐寺。這也是在 Windows 平臺下編譯動態(tài)庫經(jīng)常出現(xiàn)的問題,如果我們的源碼是在 Linux 平臺下編寫的話盖奈,更是很容易忘記修改源碼混坞。以下是一個導(dǎo)出 MyClass 的例子:


class __declspec(dllexport) MyClass {

public:

    static void MyPrint();

};

除了導(dǎo)出符號標(biāo)識符 __declspec(dllexport) 以外,我們作為用戶使用動態(tài)庫的時候钢坦,對應(yīng)頭文件中的符號還需要有 __declspec(dllimport) 標(biāo)識符來表示這個符號是從動態(tài)庫導(dǎo)入的究孕。對應(yīng)上面的 MyClass 這個例子,我們包含的頭文件應(yīng)該有以下內(nèi)容:


class __declspec(dllimport) MyClass {

public:

    static void MyPrint();

};

一般對于一個庫文件我們并不想對導(dǎo)入和導(dǎo)出分別寫兩個幾乎同樣的頭文件爹凹,因此往往使用宏來替代直接使用 __declspec(dllexport)__declspec(dllimport) 關(guān)鍵字厨诸。即:


#pragma once

#ifdef MY_LIB_EXPORTS

#define MY_LIB_API __declspec(dllexport)

#else

#define MY_LIB_API __declspec(dllimport)

#endif

class MY_LIB_API MyClass {

public:

    static void MyPrint();

};

這樣我們只需要在編譯(導(dǎo)出)這個庫的時候,給編譯器添加 MY_LIB_EXPORTS 宏禾酱。而在使用該庫的時候什么都不定義即可微酬。

編寫 export 頭文件

了解了 windows 平臺下頭文件的導(dǎo)出規(guī)則之后绘趋,現(xiàn)在我們來編寫一個名為 my_lib_export.h 的頭文件來專門的控制 MY_LIB_API 宏的定義。注意到上面的討論只是在 Windows 平臺以及編寫動態(tài)庫時才會發(fā)生得封,對應(yīng) Linux 平臺以及編寫靜態(tài)庫時埋心,我們按照教科書上的定義按部就班的寫我們的 C++ 代碼即可。為了使 MY_LIB_API 的定義同時考慮到 Linux 平臺以及靜態(tài)庫的的情況忙上。這里豐富 MY_LIB_API 的定義如下(my_lib_export.h 中的內(nèi)容):


#pragma once

#ifdef MY_LIB_SHARED_BUILD

#ifdef _WIN32

#ifdef MY_LIB_EXPORTS

#define MY_LIB_API __declspec(dllexport)

#else

#define MY_LIB_API __declspec(dllimport)

#endif  // MY_LIB_EXPORTS

#else

#define MY_LIB_API

#endif  // _WIN32

#else

#define MY_LIB_API

#endif  // MY_LIB_SHARED_BUILD

這里除了使用 MY_LIB_EXPORTS 宏來判斷是否為導(dǎo)出動態(tài)庫以外拷呆,還使用到了編譯器自帶的 _WIN32 宏來判斷是否是在 windows 平臺上以及使用了需要我們自己定義的另外一個宏 MY_LIB_SHARED_BUILD 來判斷是否正在編譯動態(tài)庫。除了上一節(jié)討論的情況以外疫粥,我們均將 MY_LIB_API 的值設(shè)置為了空茬斧,即什么都沒有定義。此時和普通的類定義完全相同梗逮。有了這個頭文件之后项秉,我們只需要在導(dǎo)出符號表的頭文件中包含該頭文件,就可以使用 MY_LIB_API 宏了慷彤。

關(guān)于 MY_LIB_SHARED_BUILDMY_LIB_EXPORTS 宏的定義娄蔼,我將在下面 CMakeLists.txt 的編寫一節(jié)進(jìn)行介紹。最后在多介紹一點底哗,事實上 my_lib_export.h 這個頭文件是可以通過 CMake 提供的 GenerateExportHeader 命令自動生成的岁诉。考慮到自動生成的額外配置以及生成后的文件多出的無關(guān)內(nèi)容不易于理解跋选,這里不對該命令的使用進(jìn)行介紹了涕癣,如果有興趣的話,可以自行了解前标。以下內(nèi)容摘抄自一個自動生成的 export 頭文件坠韩,可以看出其表達(dá)的內(nèi)容和我們上面自行定義的基本相同:


#ifndef LOGGING_EXPORT_H

#define LOGGING_EXPORT_H

#ifdef LOGGING_STATIC_DEFINE

#  define LOGGING_EXPORT

#  define LOGGING_NO_EXPORT

#else

#  ifndef LOGGING_EXPORT

#    ifdef logging_EXPORTS

        /* We are building this library */

#      define LOGGING_EXPORT __declspec(dllexport)

#    else

        /* We are using this library */

#      define LOGGING_EXPORT __declspec(dllimport)

#    endif

#  endif

#  ifndef LOGGING_NO_EXPORT

#    define LOGGING_NO_EXPORT

#  endif

#endif

#ifndef LOGGING_DEPRECATED

#  define LOGGING_DEPRECATED __declspec(deprecated)

#endif

#ifndef LOGGING_DEPRECATED_EXPORT

#  define LOGGING_DEPRECATED_EXPORT LOGGING_EXPORT LOGGING_DEPRECATED

#endif

#ifndef LOGGING_DEPRECATED_NO_EXPORT

#  define LOGGING_DEPRECATED_NO_EXPORT LOGGING_NO_EXPORT LOGGING_DEPRECATED

#endif

#if 0 /* DEFINE_NO_DEPRECATED */

#  ifndef LOGGING_NO_DEPRECATED

#    define LOGGING_NO_DEPRECATED

#  endif

#endif

#endif /* LOGGING_EXPORT_H */

編寫 CMakeLists 文件

上面的內(nèi)容介紹了如何修改源碼:

  • 編寫 my_lib_export.h 頭文件在其中定義 MY_LIB_API 宏。

  • 在需要導(dǎo)出的符號前炼列,加上 MY_LIB_API 宏只搁。

接下來將介紹如何編寫 CMakeLists.txt 文件,其實只需要在 CMakeLists.txt 文件中做兩件事:

  • 添加是否編譯動態(tài)庫選項俭尖。

  • 定義相關(guān)的宏幫助源碼“理解”應(yīng)該如何定義 MY_LIB_API 宏须蜗。

首先頂層 CMakeLists.txt 文件中需要添加如下語句來添加編譯選項:


option(BUILD_SHARED_LIBS "Specifies the type of libraries (SHARED or STATIC) to build" OFF)

其次在編譯庫的 CMakeLists.txt 文件中需要根據(jù)指定的編譯選項,來定義不同的編譯形式以及宏定義:


if (BUILD_SHARED_LIBS)

    add_library(my_lib SHARED ${SrcFiles})

    target_compile_definitions(my_lib PUBLIC -DMY_LIB_SHARED_BUILD)

    target_compile_definitions(my_lib PRIVATE -DMY_LIB_EXPORTS)

else()

    add_library(my_lib STATIC ${SrcFiles})

endif()

這里在編譯動態(tài)庫的時候會添加兩個宏定義 MY_LIB_SHARED_BUILD 以及 MY_LIB_EXPORTS目溉。注意到這里 MY_LIB_EXPORTS 宏定義的訪問符為 PRIVATE 即這個宏定義只是在編譯時有效明肮。而 MY_LIB_SHARED_BUILD 宏定義的訪問符為 PUBLIC ,即無論是編譯還是安裝后作為庫文件引用時均有效缭付。這樣我們就可以保證源碼中 MY_LIB_API 宏被正確定義了柿估。

完整的 CMakeLists.txt 文件和源碼可以在 github 倉庫 中查看。

參考資料:

GenerateExportHeader

Writing a Cross-Platform Dynamic Library

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末陷猫,一起剝皮案震驚了整個濱河市秫舌,隨后出現(xiàn)的幾起案子的妖,更是在濱河造成了極大的恐慌,老刑警劉巖足陨,帶你破解...
    沈念sama閱讀 212,686評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嫂粟,死亡現(xiàn)場離奇詭異,居然都是意外死亡墨缘,警方通過查閱死者的電腦和手機(jī)星虹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镊讼,“玉大人宽涌,你說我怎么就攤上這事〉澹” “怎么了卸亮?”我有些...
    開封第一講書人閱讀 158,160評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長玩裙。 經(jīng)常有香客問我兼贸,道長,這世上最難降的妖魔是什么吃溅? 我笑而不...
    開封第一講書人閱讀 56,736評論 1 284
  • 正文 為了忘掉前任溶诞,我火速辦了婚禮,結(jié)果婚禮上罕偎,老公的妹妹穿的比我還像新娘。我一直安慰自己京闰,他們只是感情好颜及,可當(dāng)我...
    茶點故事閱讀 65,847評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蹂楣,像睡著了一般俏站。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上痊土,一...
    開封第一講書人閱讀 50,043評論 1 291
  • 那天肄扎,我揣著相機(jī)與錄音,去河邊找鬼赁酝。 笑死犯祠,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的酌呆。 我是一名探鬼主播衡载,決...
    沈念sama閱讀 39,129評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼隙袁!你這毒婦竟也來了痰娱?” 一聲冷哼從身側(cè)響起弃榨,我...
    開封第一講書人閱讀 37,872評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梨睁,沒想到半個月后鲸睛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,318評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡坡贺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,645評論 2 327
  • 正文 我和宋清朗相戀三年官辈,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拴念。...
    茶點故事閱讀 38,777評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡钧萍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出政鼠,到底是詐尸還是另有隱情风瘦,我是刑警寧澤,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布公般,位于F島的核電站万搔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏官帘。R本人自食惡果不足惜瞬雹,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望刽虹。 院中可真熱鬧酗捌,春花似錦、人聲如沸涌哲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阀圾。三九已至哪廓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間初烘,已是汗流浹背涡真。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留肾筐,地道東北人哆料。 一個月前我還...
    沈念sama閱讀 46,589評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像吗铐,于是被迫代替她去往敵國和親剧劝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,687評論 2 351

推薦閱讀更多精彩內(nèi)容

  • CMake學(xué)習(xí) 本篇分享一下有關(guān)CMake的一些學(xué)習(xí)心得以及相關(guān)使用抓歼。 本文目錄如下: [1讥此、CMake介紹] [...
    AlphaGL閱讀 12,230評論 11 79
  • 前段時間由于做比賽的事拢锹,一直都沒時間寫博客,現(xiàn)在終于可以補(bǔ)上一篇了萄喳,一直想學(xué)習(xí)一點NDK開發(fā)的知識卒稳,但是遲遲沒有動...
    冰鑒IT閱讀 1,751評論 7 18
  • 本文不介紹cmake命令使用方法,也不講CMakeLists.txt的語法他巨,有需要的讀者可以看我另外相關(guān)的文章即可...
    konishi5202閱讀 1,105評論 0 5
  • 寫這篇文章充坑,來記錄下如何在Linux平臺下使用NDK編譯出能夠在AndroidStudio中使用的動態(tài)庫。 在進(jìn)入...
    凌煙醉臥閱讀 2,597評論 0 0
  • 在Android Studio 2.2開始染突,正式支持cmake編譯捻爷,在與android studio結(jié)合之前,cm...
    蛋西閱讀 5,615評論 0 3