在 CMakeLists.txt 中添加编译动态库选项

在 CMakeLists.txt 中添加编译动态库选项

在使用 CMake 构建项目时,一个常见的应用就是使用 CMake 编译一个库文件了。而编译成一个动态库或者静态库又是编译库文件时经常使用的一个选项。本文介绍了如何在 CMake 中添加一个选项来控制是否将库编译为动态库,且该选项可以和 CMake 一样跨平台使用。

本文并不从动态库和静态库的起源开始讲起,因此有些预备知识需要你提前了解。这些知识不需要你深刻的了解并实践过,只需看过相关文章有所了解即可。这些预备知识包括:

  • C++ 基本编程知识。

  • 什么是动态库。

  • 动态库和静态库的区别。

  • CMake 是干什么的,CMake 的基本知识。

上面的预备知识可以在网上很容易查阅得到,Static vs Dynamic Libraries 这篇英文文章介绍的也比较详细,可以作为参考。

主要难点

关于在 CMake 中添加动态库的选项,主要的难点就在于 windows 平台上的改动较大。在 linux 平台下,动态库和静态库的源代码是完全一样的,只需要修改编译参数即可。而 windows 平台下,如果想把静态库变成动态库的话,所有的源码都需要改变!因此如果你之前写了大量代码而没有考虑到 windows 平台上编译动态库的话,修改起来可能会是一个很大的工程。本文将以一个简单的实例来介绍如何编写一个可跨平台的带动静态编译参数的 CMake 项目。示例代码可以在 这里 查看。

Windows 平台下的动态库导入导出

前面我们提到过,在 Windows 平台中生成动态库其源码和静态库是不同的。在 Windows 平台中,我们导出动态库时,除了会生成 .dll 动态库之外还会生成一个 .lib 文件。这个 .lib 文件和静态库的 .lib 文件不同,它里面并不保存代码生成的二进制文件,而是所有需要导出符号的符号表。因此这个 .lib 文件和编译静态库生成的 .lib 文件相比会小很多。而这个导出的符号表是需要我们在源码中进行指定的。如果我们希望将将一个符号(symbol)导出(这里的符号可以指类、函数等各种类型),需要在其前面加上 __declspec(dllexport) 标志。这样这个符号的相关信息就会导出的 .lib 中的符号表中了。如果我们的源码中没有任何 __declspec(dllexport) 的话,我们依然可以成功的编译出动态库,但是并不会生成保存符号表的.lib 文件。这也是在 Windows 平台下编译动态库经常出现的问题,如果我们的源码是在 Linux 平台下编写的话,更是很容易忘记修改源码。以下是一个导出 MyClass 的例子:


class __declspec(dllexport) MyClass {

public:

    static void MyPrint();

};

除了导出符号标识符 __declspec(dllexport) 以外,我们作为用户使用动态库的时候,对应头文件中的符号还需要有 __declspec(dllimport) 标识符来表示这个符号是从动态库导入的。对应上面的 MyClass 这个例子,我们包含的头文件应该有以下内容:


class __declspec(dllimport) MyClass {

public:

    static void MyPrint();

};

一般对于一个库文件我们并不想对导入和导出分别写两个几乎同样的头文件,因此往往使用宏来替代直接使用 __declspec(dllexport)__declspec(dllimport) 关键字。即:


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

};

这样我们只需要在编译(导出)这个库的时候,给编译器添加 MY_LIB_EXPORTS 宏。而在使用该库的时候什么都不定义即可。

编写 export 头文件

了解了 windows 平台下头文件的导出规则之后,现在我们来编写一个名为 my_lib_export.h 的头文件来专门的控制 MY_LIB_API 宏的定义。注意到上面的讨论只是在 Windows 平台以及编写动态库时才会发生,对应 Linux 平台以及编写静态库时,我们按照教科书上的定义按部就班的写我们的 C++ 代码即可。为了使 MY_LIB_API 的定义同时考虑到 Linux 平台以及静态库的的情况。这里丰富 MY_LIB_API 的定义如下(my_lib_export.h 中的内容):


#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 宏来判断是否为导出动态库以外,还使用到了编译器自带的 _WIN32 宏来判断是否是在 windows 平台上以及使用了需要我们自己定义的另外一个宏 MY_LIB_SHARED_BUILD 来判断是否正在编译动态库。除了上一节讨论的情况以外,我们均将 MY_LIB_API 的值设置为了空,即什么都没有定义。此时和普通的类定义完全相同。有了这个头文件之后,我们只需要在导出符号表的头文件中包含该头文件,就可以使用 MY_LIB_API 宏了。

关于 MY_LIB_SHARED_BUILDMY_LIB_EXPORTS 宏的定义,我将在下面 CMakeLists.txt 的编写一节进行介绍。最后在多介绍一点,事实上 my_lib_export.h 这个头文件是可以通过 CMake 提供的 GenerateExportHeader 命令自动生成的。考虑到自动生成的额外配置以及生成后的文件多出的无关内容不易于理解,这里不对该命令的使用进行介绍了,如果有兴趣的话,可以自行了解。以下内容摘抄自一个自动生成的 export 头文件,可以看出其表达的内容和我们上面自行定义的基本相同:


#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 文件

上面的内容介绍了如何修改源码:

  • 编写 my_lib_export.h 头文件在其中定义 MY_LIB_API 宏。

  • 在需要导出的符号前,加上 MY_LIB_API 宏。

接下来将介绍如何编写 CMakeLists.txt 文件,其实只需要在 CMakeLists.txt 文件中做两件事:

  • 添加是否编译动态库选项。

  • 定义相关的宏帮助源码“理解”应该如何定义 MY_LIB_API 宏。

首先顶层 CMakeLists.txt 文件中需要添加如下语句来添加编译选项:


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

其次在编译库的 CMakeLists.txt 文件中需要根据指定的编译选项,来定义不同的编译形式以及宏定义:


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

这里在编译动态库的时候会添加两个宏定义 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容

  • CMake学习 本篇分享一下有关CMake的一些学习心得以及相关使用。 本文目录如下: [1、CMake介绍] [...
    AlphaGL阅读 12,230评论 11 79
  • 前段时间由于做比赛的事,一直都没时间写博客,现在终于可以补上一篇了,一直想学习一点NDK开发的知识,但是迟迟没有动...
    冰鉴IT阅读 1,751评论 7 18
  • 本文不介绍cmake命令使用方法,也不讲CMakeLists.txt的语法,有需要的读者可以看我另外相关的文章即可...
    konishi5202阅读 1,103评论 0 5
  • 写这篇文章,来记录下如何在Linux平台下使用NDK编译出能够在AndroidStudio中使用的动态库。 在进入...
    凌烟醉卧阅读 2,597评论 0 0
  • 在Android Studio 2.2开始,正式支持cmake编译,在与android studio结合之前,cm...
    蛋西阅读 5,607评论 0 3