[CMake翻译]使用新的CMake export all feature在Windows上创建不需要declspec()的dlls

1,876 阅读5分钟

原文地址:blog.kitware.com/create-dlls…

原文作者:blog.kitware.com/author/bill…

发布时间:2015年7月24日

CMake 3.4 将会有一个新的功能来简化从 Linux/UNIX 使用共享库将 C 和 C++ 软件移植到 Windows 的过程。Linux/UNIX开发者经常惊讶地发现,在Windows上创建一个被称为DLL(动态链接库)的共享库,需要修改源代码或明确列出dll将导出的所有符号。 Linux/UNIX上的编译器有能力自动导出共享库中的所有符号。在Windows上,你必须使用编译器指令__declspec(import)__declspec(export)来声明哪些符号会从共享库中导出/导入,或者你必须创建一个模块定义文本文件(.def),其中包含你想要导出的所有符号的列表,并将该文件传递给链接器。

对于C库,手工或自动创建一个.def文件并不困难,因为你只需要列出库中所有函数的名称。然而,对于C++代码,由于名称的混乱和函数的数量,手工制作一个.def文件几乎是不可能的。标准的变通方法是使用预处理器有条件地在代码中插入__declspec(import)__declspec(export)。 然而,对于一个庞大的现有C++代码库,编辑所有的源代码是很困难的,也是很耗时的。CMake现在有一个功能,允许它查询组成DLL的.obj文件,并自动创建一个.def文件,在大多数情况下,不需要修改原始源代码。

这个功能在CMake中通过一个新的目标属性WINDOWS_EXPORT_ALL_SYMBOLS实现。当启用时,这个属性会使 CMake 自动创建一个 .def 文件,其中包含在 Windows 上 SHARED 库的输入 .obj 文件中发现的所有符号。 这个 .def 文件将被传递给链接器,使所有符号从 DLL 中导出。对于全局数据符号,当针对DLL中的代码进行编译时,仍然必须使用__declspec(dllimport)。符号会正确地自动从 DLL 中导出,但编译器需要在编译时知道它是从 DLL 中导入的。 所有其他的函数符号将被调用者自动导出和导入。 这简化了将项目移植到Windows的过程,减少了对显式dllexport标记的需求,即使是在C++类中也是如此。当创建一个目标时,这个属性由CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS变量的值初始化。

要在现有的项目中尝试这个功能,请运行以下命令

cmake -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE -DBUILD_SHARED_LIBS=TRUE

在 CMake 项目上。 这将把所有没有明确指定构建类型的 add_library 调用变成共享构建。如果项目中没有全局数据变量,所有的库都会以 DLLs 的形式构建,不会出现错误。 如果遇到未定义的符号,请检查全局数据,比如类的静态数据成员。处理全局数据最简单的方法是使用CMake GenerateExportHeader模块,像这样。

add_library(mylibrary ${mylibrary_SOURCES})
# add these lines
include(GenerateExportHeader)
generate_export_header(mylibrary)

像这样编辑源代码。

#include <mylibary_export.h>
class myclass
{
 static mylibrary_EXPORT int GlobalCounter;
…

注意,如果你使用GenerateExportHeader,并且仍然希望静态构建工作,你需要在静态构建过程中添加一个-Dmylibrary_STATIC。更多细节请参见生成的mylibary_export.h。

实际应用案例

在给CMake添加了这个功能之后,我在两个比较大的软件项目VXL (vxl.sourceforge.net/ )和ITK (www.itk.org )上试了一下。虽然,这确实花费了一些时间,但比起在每个源文件中添加dll标记要容易得多。最后,所需要的改动属于以下几个反复出现的问题和修复。

  • 处理类静态数据成员
    • 在静态数据中增加dll导入/导出宏。
    • 消除静态数据 -
      • 这可以通过用一个本地静态变量做一个静态getter函数来实现。
  • 静态成员
    • 这些都不是新功能所能导出的。 在某些情况下,为了支持旧的编译器,这些被放在.cxx文件中并编译到库中,而不是仅仅内联在.h文件中。 如果你有这种情况,你需要使用dllimport和dllexport标记,而不是仅仅使用dllimport标记,因为自动导出将无法工作。
  • 从 std::string 或其他模板类继承 dllimport/dllexport 标记。
    • 这在某些情况下会导致父模板类中出现重复的符号错误。解决这个问题的方法是删除标记,让自动导出做它的事情。
class mylibrary_EXPORT myclass public std::string
{
..
  • 没有完全链接的库。在许多Unix系统上,链接器会允许共享库在创建时有未解析的符号,这些符号在可执行文件的链接时应该被解析。 Windows的dll必须解析所有的符号,所以你的库必须显式地链接到它所依赖的一切。 如果你在链接时得到一个未定义的符号,有一种方法是在创建的.def文件中查找该符号在大系统中的存在。从bash(cygwin或git)shell中,你可以运行这样的程序。
 find . -name "*.def" | xargs grep symbol
  • 模板化类的静态数据成员。 最好是直接避开这些,或者只对这些使用显式实例化或特殊化。 我根本无法让这些工作。

致谢

我想特别感谢Valeri Fine(代码作者)和Bertrand Bellenot以及欧洲核子研究中心ROOT团队的其他成员,感谢他们贡献了.obj文件符号解析代码,并在他们的项目上测试了该功能。请欣赏

链接

夜间CMake新功能:www.cmake.org/files/dev/c…

晚间文件为专题。

www.cmake.org/cmake/help/…

该功能的ITK变化:review.source.kitware.com/#/c/20020

此功能的VXL变化:github.com/vxl/vxl/pul…

Valeri Fine在2977年4月7日至11日的柏林高能物理计算大会中心Lichtenberger会议上提出了这种方法并介绍了原始代码。

www.ifh.de/CHEP97/abst…

www.ifh.de/CHEP97/pape…


通过www.DeepL.com/Translator (免费版)翻译