04 关于在Windows下生成动态库的注意问题
来源:http://www.tudoupe.com时间:2022-03-01
该文章主要讲解在Windows下如何生成动态库,在源码上与Linux环境下生成动态库的区别
之前在使用CMkae在Widnows下生成动态库时,遇到一些问题,总是无法编译通过,报错提示没有.lib文件,之后在网上寻求答案时, 该作者的文章 解释的较好,感谢该作者的总结,以下内容为该作者总结!
在使用 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的例子:
除了导出符号标识符__declspec(dllexport)以外,我们作为用户使用动态库的时候,对应头文件中的符号还需要有__declspec(dllimport)标识符来表示这个符号是从动态库导入的。对应上面的MyClass这个例子,我们包含的头文件应该有以下内容:
一般对于一个库文件我们并不想对导入和导出分别写两个几乎同样的头文件,因此往往使用宏来替代直接使用__declspec(dllexport)和__declspec(dllimport)关键字。即:
这样我们只需要在编译(导出)这个库的时候,给编译器添加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中的内容):
这里除了使用MY_LIB_EXPORTS宏来判断是否为导出动态库以外,还使用到了编译器自带的_WIN32宏来判断是否是在 windows 平台上以及使用了需要我们自己定义的另外一个宏MY_LIB_SHARED_BUILD来判断是否正在编译动态库。除了上一节讨论的情况以外,我们均将MY_LIB_API的值设置为了空,即什么都没有定义。此时和普通的类定义完全相同。有了这个头文件之后,我们只需要在导出符号表的头文件中包含该头文件,就可以使用MY_LIB_API宏了。
关于MY_LIB_SHARED_BUILD和MY_LIB_EXPORTS宏的定义,我将在下面CMakeLists.txt的编写一节进行介绍。最后在多介绍一点,事实上my_lib_export.h
这个头文件是可以通过 CMake 提供的
GenerateExportHeader
命令自动生成的。考虑到自动生成的额外配置以及生成后的文件多出的无关内容不易于理解,这里不对该命令的使用进行介绍了,如果有兴趣的话,可以自行了解。以下内容摘抄自一个自动生成的 export 头文件,可以看出其表达的内容和我们上面自行定义的基本相同:
编写 CMakeLists 文件
上面的内容介绍了如何修改源码:
- 编写my_lib_export.h头文件在其中定义
MY_LIB_API宏。 - 在需要导出的符号前,加上
MY_LIB_API宏。
接下来将介绍如何编写CMakeLists.txt文件,其实只需要在CMakeLists.txt文件中做两件事:
- 添加是否编译动态库选项。
- 定义相关的宏帮助源码“理解”应该如何定义
MY_LIB_API宏。
首先顶层CMakeLists.txt文件中需要添加如下语句来添加编译选项:
其次在编译库的CMakeLists.txt文件中需要根据指定的编译选项,来定义不同的编译形式以及宏定义:
这里在编译动态库的时候会添加两个宏定义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
相关新闻
- 2023-04-16 2台电脑怎么共享(2台电脑怎么共享
- 2023-04-16 主板检测卡代码(电脑主板检测卡代
- 2023-04-16 dnf未响应(dnf未响应老是上不去)
- 2023-04-16 ppoe(pppoe拨号上网)
- 2023-04-16 网速不稳定(网速不稳定是路由器的
- 2023-04-16 wds状态(Wds状态成功)
- 2023-04-16 光标键(光标键不动了怎么办)
- 2023-04-16 电脑提速(电脑提速100倍的方法)
- 2023-04-16 切换用户(切换用户怎么切换回来
- 2023-04-16 数据包是什么(产品数据包是什么
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
