在 CMakeLists.txt 中添加編譯動(dòng)態(tài)庫(kù)選項(xiàng)

在 CMakeLists.txt 中添加編譯動(dòng)態(tài)庫(kù)選項(xiàng)

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

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

  • C++ 基本編程知識(shí)。

  • 什么是動(dòng)態(tài)庫(kù)。

  • 動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)的區(qū)別。

  • CMake 是干什么的,CMake 的基本知識(shí)。

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

主要難點(diǎn)

關(guān)于在 CMake 中添加動(dòng)態(tài)庫(kù)的選項(xiàng),主要的難點(diǎn)就在于 windows 平臺(tái)上的改動(dòng)較大。在 linux 平臺(tái)下,動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)的源代碼是完全一樣的,只需要修改編譯參數(shù)即可。而 windows 平臺(tái)下,如果想把靜態(tài)庫(kù)變成動(dòng)態(tài)庫(kù)的話,所有的源碼都需要改變!因此如果你之前寫(xiě)了大量代碼而沒(méi)有考慮到 windows 平臺(tái)上編譯動(dòng)態(tài)庫(kù)的話,修改起來(lái)可能會(huì)是一個(gè)很大的工程。本文將以一個(gè)簡(jiǎn)單的實(shí)例來(lái)介紹如何編寫(xiě)一個(gè)可跨平臺(tái)的帶動(dòng)靜態(tài)編譯參數(shù)的 CMake 項(xiàng)目。示例代碼可以在 這里 查看。

Windows 平臺(tái)下的動(dòng)態(tài)庫(kù)導(dǎo)入導(dǎo)出

前面我們提到過(guò),在 Windows 平臺(tái)中生成動(dòng)態(tài)庫(kù)其源碼和靜態(tài)庫(kù)是不同的。在 Windows 平臺(tái)中,我們導(dǎo)出動(dòng)態(tài)庫(kù)時(shí),除了會(huì)生成 .dll 動(dòng)態(tài)庫(kù)之外還會(huì)生成一個(gè) .lib 文件。這個(gè) .lib 文件和靜態(tài)庫(kù)的 .lib 文件不同,它里面并不保存代碼生成的二進(jìn)制文件,而是所有需要導(dǎo)出符號(hào)的符號(hào)表。因此這個(gè) .lib 文件和編譯靜態(tài)庫(kù)生成的 .lib 文件相比會(huì)小很多。而這個(gè)導(dǎo)出的符號(hào)表是需要我們?cè)谠创a中進(jìn)行指定的。如果我們希望將將一個(gè)符號(hào)(symbol)導(dǎo)出(這里的符號(hào)可以指類(lèi)、函數(shù)等各種類(lèi)型),需要在其前面加上 __declspec(dllexport) 標(biāo)志。這樣這個(gè)符號(hào)的相關(guān)信息就會(huì)導(dǎo)出的 .lib 中的符號(hào)表中了。如果我們的源碼中沒(méi)有任何 __declspec(dllexport) 的話,我們依然可以成功的編譯出動(dòng)態(tài)庫(kù),但是并不會(huì)生成保存符號(hào)表的.lib 文件。這也是在 Windows 平臺(tái)下編譯動(dòng)態(tài)庫(kù)經(jīng)常出現(xiàn)的問(wèn)題,如果我們的源碼是在 Linux 平臺(tái)下編寫(xiě)的話,更是很容易忘記修改源碼。以下是一個(gè)導(dǎo)出 MyClass 的例子:


class __declspec(dllexport) MyClass {

public:

    static void MyPrint();

};

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


class __declspec(dllimport) MyClass {

public:

    static void MyPrint();

};

一般對(duì)于一個(gè)庫(kù)文件我們并不想對(duì)導(dǎo)入和導(dǎo)出分別寫(xiě)兩個(gè)幾乎同樣的頭文件,因此往往使用宏來(lái)替代直接使用 __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)出)這個(gè)庫(kù)的時(shí)候,給編譯器添加 MY_LIB_EXPORTS 宏。而在使用該庫(kù)的時(shí)候什么都不定義即可。

編寫(xiě) export 頭文件

了解了 windows 平臺(tái)下頭文件的導(dǎo)出規(guī)則之后,現(xiàn)在我們來(lái)編寫(xiě)一個(gè)名為 my_lib_export.h 的頭文件來(lái)專(zhuān)門(mén)的控制 MY_LIB_API 宏的定義。注意到上面的討論只是在 Windows 平臺(tái)以及編寫(xiě)動(dòng)態(tài)庫(kù)時(shí)才會(huì)發(fā)生,對(duì)應(yīng) Linux 平臺(tái)以及編寫(xiě)靜態(tài)庫(kù)時(shí),我們按照教科書(shū)上的定義按部就班的寫(xiě)我們的 C++ 代碼即可。為了使 MY_LIB_API 的定義同時(shí)考慮到 Linux 平臺(tái)以及靜態(tài)庫(kù)的的情況。這里豐富 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 宏來(lái)判斷是否為導(dǎo)出動(dòng)態(tài)庫(kù)以外,還使用到了編譯器自帶的 _WIN32 宏來(lái)判斷是否是在 windows 平臺(tái)上以及使用了需要我們自己定義的另外一個(gè)宏 MY_LIB_SHARED_BUILD 來(lái)判斷是否正在編譯動(dòng)態(tài)庫(kù)。除了上一節(jié)討論的情況以外,我們均將 MY_LIB_API 的值設(shè)置為了空,即什么都沒(méi)有定義。此時(shí)和普通的類(lèi)定義完全相同。有了這個(gè)頭文件之后,我們只需要在導(dǎo)出符號(hào)表的頭文件中包含該頭文件,就可以使用 MY_LIB_API 宏了。

關(guān)于 MY_LIB_SHARED_BUILDMY_LIB_EXPORTS 宏的定義,我將在下面 CMakeLists.txt 的編寫(xiě)一節(jié)進(jìn)行介紹。最后在多介紹一點(diǎn),事實(shí)上 my_lib_export.h 這個(gè)頭文件是可以通過(guò) CMake 提供的 GenerateExportHeader 命令自動(dòng)生成的??紤]到自動(dòng)生成的額外配置以及生成后的文件多出的無(wú)關(guān)內(nèi)容不易于理解,這里不對(duì)該命令的使用進(jìn)行介紹了,如果有興趣的話,可以自行了解。以下內(nèi)容摘抄自一個(gè)自動(dòng)生成的 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 */

編寫(xiě) CMakeLists 文件

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

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

  • 在需要導(dǎo)出的符號(hào)前,加上 MY_LIB_API 宏。

接下來(lái)將介紹如何編寫(xiě) CMakeLists.txt 文件,其實(shí)只需要在 CMakeLists.txt 文件中做兩件事:

  • 添加是否編譯動(dòng)態(tài)庫(kù)選項(xiàng)。

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

首先頂層 CMakeLists.txt 文件中需要添加如下語(yǔ)句來(lái)添加編譯選項(xiàng):


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

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


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()

這里在編譯動(dòng)態(tài)庫(kù)的時(shí)候會(huì)添加兩個(gè)宏定義 MY_LIB_SHARED_BUILD 以及 MY_LIB_EXPORTS。注意到這里 MY_LIB_EXPORTS 宏定義的訪問(wèn)符為 PRIVATE 即這個(gè)宏定義只是在編譯時(shí)有效。而 MY_LIB_SHARED_BUILD 宏定義的訪問(wèn)符為 PUBLIC ,即無(wú)論是編譯還是安裝后作為庫(kù)文件引用時(shí)均有效。這樣我們就可以保證源碼中 MY_LIB_API 宏被正確定義了。

完整的 CMakeLists.txt 文件和源碼可以在 github 倉(cāng)庫(kù) 中查看。

參考資料:

GenerateExportHeader

Writing a Cross-Platform Dynamic Library

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • CMake學(xué)習(xí) 本篇分享一下有關(guān)CMake的一些學(xué)習(xí)心得以及相關(guān)使用。 本文目錄如下: [1、CMake介紹] [...
    AlphaGL閱讀 12,440評(píng)論 11 79
  • 前段時(shí)間由于做比賽的事,一直都沒(méi)時(shí)間寫(xiě)博客,現(xiàn)在終于可以補(bǔ)上一篇了,一直想學(xué)習(xí)一點(diǎn)NDK開(kāi)發(fā)的知識(shí),但是遲遲沒(méi)有動(dòng)...
    冰鑒IT閱讀 1,980評(píng)論 7 18
  • 本文不介紹cmake命令使用方法,也不講CMakeLists.txt的語(yǔ)法,有需要的讀者可以看我另外相關(guān)的文章即可...
    konishi5202閱讀 1,207評(píng)論 0 5
  • 寫(xiě)這篇文章,來(lái)記錄下如何在Linux平臺(tái)下使用NDK編譯出能夠在AndroidStudio中使用的動(dòng)態(tài)庫(kù)。 在進(jìn)入...
    凌煙醉臥閱讀 2,779評(píng)論 0 0
  • 在Android Studio 2.2開(kāi)始,正式支持cmake編譯,在與android studio結(jié)合之前,cm...
    蛋西閱讀 6,430評(píng)論 0 3

友情鏈接更多精彩內(nèi)容