GenerateExportHeader

此模块提供用于生成包含预处理器宏定义的头文件的命令,以控制 C/C++ 符号可见性。

在 CMake 项目中加载此模块,使用

include(GenerateExportHeader)

3.12 版本新增: 支持 C 项目。先前版本仅支持 C++ 项目。

在开发 C 或 C++ 项目时,尤其是跨平台使用时,符号可见性决定了哪些函数、类、全局变量、模板和其他符号对库的用户可见。

例如,在 Windows 上,在构建共享库时必须显式使用 __declspec(dllexport) 标记符号,在使用共享库时则使用 __declspec(dllimport) 标记。其他平台可能使用 __attribute__((visibility("default"))) 等属性。

此模块简化了用于管理这些要求的预处理器宏的创建和使用,避免了源代码中重复且容易出错的 #ifdef 块。

一些符号可见性也可以通过编译器选项进行控制。在 CMake 中,如 <LANG>_VISIBILITY_PRESETVISIBILITY_INLINES_HIDDEN 等目标属性,可以在适当时启用编译器可见性标志。另请参阅相关的便利变量 CMAKE_<LANG>_VISIBILITY_PRESETCMAKE_VISIBILITY_INLINES_HIDDEN,以便在当前范围内为所有目标启用它。这些通常与此模块结合使用,以进一步简化 C/C++ 代码,从而无需在源代码中使用一些预处理器宏。

命令

本模块提供以下命令

生成导出头文件

generate_export_header

生成一个适合包含在源代码中的头文件,其中包含用于控制符号可见性的预处理器导出宏。

generate_export_header(
  <target>
  [BASE_NAME <base-name>]
  [EXPORT_FILE_NAME <export-file-name>]
  [EXPORT_MACRO_NAME <export-macro-name>]
  [NO_EXPORT_MACRO_NAME <no-export-macro-name>]
  [DEPRECATED_MACRO_NAME <deprecated-macro-name>]
  [DEFINE_NO_DEPRECATED]
  [NO_DEPRECATED_MACRO_NAME <no-deprecated-macro-name>]
  [STATIC_DEFINE <static-define>]
  [PREFIX_NAME <prefix>]
  [CUSTOM_CONTENT_FROM_VARIABLE <variable>]
  [INCLUDE_GUARD_NAME <include-guard-name>]
)

默认情况下,此命令在当前二进制目录(CMAKE_CURRENT_BINARY_DIR)中生成一个名为 <target-name-lowercase>_export.h 的头文件。此头文件定义了一组预处理器宏,用于在不同平台和构建类型(例如,静态或共享构建)中将 API 符号标记为导出、隐藏或已弃用,并打算与库的公共头文件一起安装,因为它会影响公共 API 声明。

  • <MACRO>_EXPORT:标记用于导出或导入的符号,使其在构建或使用共享库时作为公共 API 的一部分可见。

  • <MACRO>_NO_EXPORT:标记不应导出的符号。如果 <LANG>_VISIBILITY_PRESET 目标属性设置为 hidden,则在源代码中使用此宏通常是多余的。

  • <MACRO>_DEPRECATED:将符号标记为已弃用。当使用此类符号时,编译器会在编译时发出警告。

  • <MACRO>_DEPRECATED_EXPORT:组合导出/导入和已弃用标记,用于既是公共 API 的一部分又是已弃用的符号。

  • <MACRO>_DEPRECATED_NO_EXPORT:标记一个已弃用但不应导出的符号(内部且已弃用)。

  • <MACRO>_NO_DEPRECATED:一个可用于源代码的宏,可以通过预处理器逻辑有条件地从构建中排除已弃用的代码部分。

默认情况下,<MACRO> 部分源自目标的大写名称或显式提供的 <base-name>。可以使用可选参数自定义所有宏名称。

参数为

<target>

将为其生成导出头文件的目标名称。支持的目标类型:

  • STATIC 库(在这种情况下,与导出相关的宏定义为空值)

  • SHARED

  • MODULE

  • 3.1 版本新增: OBJECT

BASE_NAME <base-name>

如果指定,则覆盖默认文件名和宏名称。

EXPORT_FILE_NAME <export-file-name>

如果指定,则将生成的导出头文件(<base-name-lowercase>_export.h)的完整路径和名称覆盖为 <export-file-name>。如果给定为相对路径,则将相对于当前二进制目录(CMAKE_CURRENT_BINARY_DIR)进行解释。

EXPORT_MACRO_NAME <export-macro-name>

如果指定,则覆盖导出指令的默认宏名称。

NO_EXPORT_MACRO_NAME <no-export-macro-name>

如果指定,则使用 <no-export-macro-name> 作为宏名称,该宏指示不应导出的项的属性。

DEPRECATED_MACRO_NAME <deprecated-macro-name>

如果指定,则将使用以下名称:

  • <deprecated-macro-name>(用于标记已弃用符号的宏)

  • <deprecated-macro-name>_EXPORT(用于带有导出标记的已弃用符号的宏)

  • <deprecated-macro-name>_NO_EXPORT(用于带有不导出标记的已弃用符号的宏)

而不是格式为 <MACRO>_DEPRECATED{,_EXPORT,_NO_EXPORT} 的默认名称。

DEFINE_NO_DEPRECATED

如果指定,则会定义一个名为 <MACRO>_NO_DEPRECATED 的宏。

NO_DEPRECATED_MACRO_NAME <no-deprecated-macro-name>

DEFINE_NO_DEPRECATED 选项结合使用。如果指定,则使用名为 <no-deprecated-macro-name> 的宏,而不是默认的 <MACRO>_NO_DEPRECATED

STATIC_DEFINE <static-define>

如果指定,则使用 <static-define> 宏名称,而不是默认的 <MACRO>_STATIC_DEFINE。此宏控制静态库的生成头文件中的符号导出行为。通常在从同一源使用单个生成的导出头文件构建库的共享和静态变体时使用。当为静态库定义此宏时,与导出相关的宏将展开为空。这对 Windows 也很重要,因为在 Windows 上,符号装饰仅对共享库是必需的,而对静态库则不需要。

PREFIX_NAME <prefix>

如果指定,则将额外的 <prefix> 前缀添加到所有生成的宏名称。

CUSTOM_CONTENT_FROM_VARIABLE <variable>

3.7 版本中新增。

如果指定,则将 <variable> 值的内容附加到生成的头文件内容中,位于预处理器宏定义之后。

INCLUDE_GUARD_NAME <include-guard-name>

3.11 版本新增。

如果指定,则使用 <include-guard-name> 作为预处理器宏名称,以防止多次包含生成的头文件,而不是默认名称 <export-macro-name>_H

<base-name-lowercase>_export.h
#ifndef <include-guard-name>
#define <include-guard-name>
// ...
#endif /* <include-guard-name> */

已弃用命令

add_compiler_export_flags

3.0 版本已弃用: 请改用设置目标属性 CXX_VISIBILITY_PRESETVISIBILITY_INLINES_HIDDEN

CMAKE_CXX_FLAGS 变量或指定的变量添加 C++ 编译器选项 -fvisibility=hidden(如果支持,则添加 -fvisibility-inlines-hidden),以默认隐藏所有符号。

add_compiler_export_flags([<output_variable>])

此命令在 Windows 上无效,因为 Windows 不需要额外的编译器标志来进行导出支持。

<output-variable>

可选变量名,将填充一个字符串,其中包含用于启用所用编译器/架构的可见性支持所需的 C++ 编译选项,以空格分隔。如果指定了此参数,则不会修改 CMAKE_CXX_FLAGS 变量。

示例

示例:生成导出头文件

以下示例演示了如何使用此模块在当前二进制目录中生成一个导出头文件(example_export.h),并在名为 example 的 C++ 库中使用它来控制符号可见性。生成的头文件定义了预处理器宏 EXAMPLE_EXPORTEXAMPLE_NO_EXPORTEXAMPLE_DEPRECATEDEXAMPLE_DEPRECATED_EXPORTEXAMPLE_DEPRECATED_NO_EXPORT,并与库的其他公共头文件一起安装。

CMakeLists.txt
cmake_minimum_required(VERSION 3.24)
project(GenerateExportHeaderExample)

# Set default visibility of all symbols to hidden
set(CMAKE_CXX_VISIBILITY_PRESET "hidden")
set(CMAKE_VISIBILITY_INLINES_HIDDEN TRUE)

add_library(example)

include(GenerateExportHeader)
generate_export_header(example)

target_sources(
  example
  PRIVATE example.cxx
  PUBLIC
    FILE_SET HEADERS
      FILES example.h
    FILE_SET generated_headers
      TYPE HEADERS
      BASE_DIRS $<TARGET_PROPERTY:example,BINARY_DIR>
      FILES ${CMAKE_CURRENT_BINARY_DIR}/example_export.h
)

target_include_directories(example PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

install(
  TARGETS example
  FILE_SET HEADERS
  FILE_SET generated_headers
)

以及在 ABI 头文件中:

example.h
#include "example_export.h"

// This class is part of the public API and is exported
class EXAMPLE_EXPORT SomeClass
{
public:
  SomeClass();
  void doSomething();

  // This method is deprecated
  EXAMPLE_DEPRECATED void legacyMethod();
};

// This function is exported and deprecated
EXAMPLE_DEPRECATED_EXPORT void legacyPublicFunction();

// This function is deprecated but not exported
EXAMPLE_DEPRECATED void legacyInternalFunction();
example.cxx
#include <iostream>
#include "example.h"

SomeClass::SomeClass() = default;

void SomeClass::doSomething()
{
  std::cout << "SomeClass::doSomething() called" << std::endl;
}

void SomeClass::legacyMethod()
{
  std::cout << "SomeClass::legacyMethod() is deprecated" << std::endl;
}

void legacyPublicFunction()
{
  std::cout << "legacyPublicFunction() is deprecated" << std::endl;
}

void internalLegacyFunction()
{
  std::cout << "legacyInternalFunction() is deprecated" << std::endl;
}

示例:自定义生成的头文件

BASE_NAME 参数可用于覆盖生成的名称和用于宏的名称。以下内容将生成一个名为 other_name_export.h 的文件,其中包含导出相关的宏,如 OTHER_NAME_EXPORTOTHER_NAME_NO_EXPORTOTHER_NAME_DEPRECATED 等。

add_library(example example.cxx)
include(GenerateExportHeader)
generate_export_header(example BASE_NAME "other_name")

BASE_NAME 可通过指定其他命令选项来覆盖。例如,以下命令创建的宏名称为 OTHER_NAME_EXPORT,而不是 EXAMPLE_EXPORT,但其他宏和生成的头文件名均设置为默认值。

add_library(example example.cxx)
include(GenerateExportHeader)
generate_export_header(example EXPORT_MACRO_NAME "OTHER_NAME_EXPORT")

以下示例创建 KDE_DEPRECATED 宏,而不是默认的 EXAMPLE_DEPRECATED

add_library(example example.cxx)
include(GenerateExportHeader)
generate_export_header(example DEPRECATED_MACRO_NAME "KDE_DEPRECATED")

DEFINE_NO_DEPRECATED 选项可用于定义一个宏,该宏可用于从预处理器输出中删除已弃用的代码。

option(EXCLUDE_DEPRECATED "Exclude deprecated parts of the library")
if(EXCLUDE_DEPRECATED)
  set(NO_BUILD_DEPRECATED DEFINE_NO_DEPRECATED)
endif()

include(GenerateExportHeader)
generate_export_header(example ${NO_BUILD_DEPRECATED})
example.h
class EXAMPLE_EXPORT SomeClass
{
public:
#ifndef EXAMPLE_NO_DEPRECATED
  EXAMPLE_DEPRECATED void legacyMethod();
#endif
};
example.cxx
#ifndef EXAMPLE_NO_DEPRECATED
void SomeClass::legacyMethod() {  }
#endif

PREFIX_NAME 参数可用于将所有生成的宏名称前加上某个前缀。例如,以下代码将生成如 VTK_SOMELIB_EXPORT 等宏。

include(GenerateExportHeader)
generate_export_header(somelib PREFIX_NAME "VTK_")

可以使用 CUSTOM_CONTENT_FROM_VARIABLE 参数将其他内容追加到生成的头文件中。

include(GenerateExportHeader)
set(content [[#include "project_api.h"]])
generate_export_header(example CUSTOM_CONTENT_FROM_VARIABLE content)

示例:构建共享库和静态库

在以下示例中,共享库和静态库都从同一源构建,并且定义了 <MACRO>_STATIC_DEFINE 宏编译定义,以确保同一个生成的导出头文件对两者都有效。

add_library(example_shared SHARED example.cxx)
add_library(example_static STATIC example.cxx)

include(GenerateExportHeader)
generate_export_header(example_shared BASE_NAME "example")

# Define macro to disable export attributes for static build
target_compile_definitions(example_static PRIVATE EXAMPLE_STATIC_DEFINE)

示例:升级已弃用命令

在 CMake 的早期版本中,使用 add_compiler_export_flags() 命令来添加符号可见性编译选项。

CMakeLists.txt
add_library(example example.cxx)

include(GenerateExportHeader)

add_compiler_export_flags(flags)
string(REPLACE " " ";" flags "${flags}")
set_property(TARGET example APPEND PROPERTY COMPILE_OPTIONS "${flags}")

generate_export_header(example)

在新代码中,使用以下目标属性来实现相同的功能:

CMakeLists.txt
add_library(example example.cxx)

include(GenerateExportHeader)

set_target_properties(
  example
  PROPERTIES
    CXX_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN TRUE
)

generate_export_header(example)

另请参阅

  • 使用 DEFINE_SYMBOL 目标属性来定制生成头文件所使用的预处理器宏名称。此宏确定库头文件是在库自身编译时包含,还是在使用它(例如,安装后)的另一个项目中使用。

  • ENABLE_EXPORTS 目标属性。

  • WINDOWS_EXPORT_ALL_SYMBOLS 目标属性。