目录
[3.3.静态 / 动态库选择](#3.3.静态 / 动态库选择)
[3.4.版本 / 路径变量(兼容原生 FindOpenSSL)](#3.4.版本 / 路径变量(兼容原生 FindOpenSSL))
[3.5.vcpkg 多配置 / 单配置适配](#3.5.vcpkg 多配置 / 单配置适配)
[3.8.applink 目标(Windows 特有)](#3.8.applink 目标(Windows 特有))
1.OpenSSLConfig.cmake
windows用vcpkg安装OpenSSL,在目录:
.\vcpkg\packages\openssl_x64-windows\share\openssl\
下有OpenSSL的cmake引入文件OpenSSLConfig.cmake,如下:

打开这个文件,源码如下:
cpp
# Generated by OpenSSL
# Commands may need to know the format version.
set(CMAKE_IMPORT_FILE_VERSION 1)
# Avoid duplicate find_package()
set(_ossl_expected_targets OpenSSL::Crypto OpenSSL::SSL
OpenSSL::applink)
set(_ossl_defined_targets)
set(_ossl_undefined_targets)
foreach(t IN LISTS _ossl_expected_targets)
if(TARGET "${t}")
LIST(APPEND _ossl_defined_targets "${t}")
else()
LIST(APPEND _ossl_undefined_targets "${t}")
endif()
endforeach()
message(DEBUG "_ossl_expected_targets = ${_ossl_expected_targets}")
message(DEBUG "_ossl_defined_targets = ${_ossl_defined_targets}")
message(DEBUG "_ossl_undefined_targets = ${_ossl_undefined_targets}")
if(NOT _ossl_undefined_targets)
# All targets are defined, we're good, just undo everything and return
unset(_ossl_expected_targets)
unset(_ossl_defined_targets)
unset(_ossl_undefined_targets)
unset(CMAKE_IMPORT_FILE_VERSION)
return()
endif()
if(_ossl_defined_targets)
# We have a mix of defined and undefined targets. This is hard to reconcile,
# and probably the result of another config, or FindOpenSSL.cmake having been
# called, or whatever. Therefore, the best course of action is to quit with a
# hard error.
message(FATAL_ERROR "Some targets defined, others not:\nNot defined: ${_ossl_undefined_targets}\nDefined: ${_ossl_defined_targets}")
endif()
unset(_ossl_expected_targets)
unset(_ossl_defined_targets)
unset(_ossl_undefined_targets)
# Set up the import path, so all other import paths are made relative this file
get_filename_component(_ossl_prefix "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(_ossl_prefix "${_ossl_prefix}" PATH)
get_filename_component(_ossl_prefix "${_ossl_prefix}" PATH)
if(_ossl_prefix STREQUAL "/")
set(_ossl_prefix "")
endif()
if(OPENSSL_USE_STATIC_LIBS)
set(_ossl_use_static_libs True)
elseif(DEFINED OPENSSL_USE_STATIC_LIBS)
# We know OPENSSL_USE_STATIC_LIBS is defined and False
if(_ossl_use_static_libs)
# OPENSSL_USE_STATIC_LIBS is explicitly false, indicating that shared libraries are
# required. However, _ossl_use_static_libs indicates that no shared libraries are
# available. The best course of action is to simply return and leave it to CMake to
# use another OpenSSL config.
unset(_ossl_use_static_libs)
unset(CMAKE_IMPORT_FILE_VERSION)
return()
endif()
endif()
# Version, copied from what find_package() gives, for compatibility with FindOpenSSL.cmake
set(OPENSSL_VERSION "${OpenSSL_VERSION}")
set(OPENSSL_VERSION_MAJOR "${OpenSSL_VERSION_MAJOR}")
set(OPENSSL_VERSION_MINOR "${OpenSSL_VERSION_MINOR}")
set(OPENSSL_VERSION_FIX "${OpenSSL_VERSION_PATCH}")
set(OPENSSL_FOUND YES)
# Directories and names
set(OPENSSL_LIBRARY_DIR "${_ossl_prefix}/lib")
set(OPENSSL_INCLUDE_DIR "${_ossl_prefix}/include")
set(OPENSSL_ENGINES_DIR "${_ossl_prefix}/lib/engines-3")
set(OPENSSL_MODULES_DIR "${_ossl_prefix}/lib/../bin")
set(OPENSSL_RUNTIME_DIR "${_ossl_prefix}/bin")
set(OPENSSL_APPLINK_SOURCE "${_ossl_prefix}/include/openssl/applink.c")
set(OPENSSL_PROGRAM "${OPENSSL_RUNTIME_DIR}/openssl.exe")
if(NOT Z_VCPKG_OPENSSL_USE_SINGLE_CONFIG)
# Prevent loop
set(Z_VCPKG_OPENSSL_USE_SINGLE_CONFIG "prevent-loop")
# Chainload vcpkg's module-based multi-config target setup
find_package(OpenSSL MODULE)
set(Z_VCPKG_OPENSSL_USE_SINGLE_CONFIG 0)
else()
# Use official single-config target setup
# Set up the imported targets
if(_ossl_use_static_libs)
add_library(OpenSSL::Crypto STATIC IMPORTED)
add_library(OpenSSL::SSL STATIC IMPORTED)
set(OPENSSL_LIBCRYPTO_STATIC "${OPENSSL_LIBRARY_DIR}/libcrypto_static.lib")
set(OPENSSL_LIBCRYPTO_DEPENDENCIES ws2_32.lib gdi32.lib advapi32.lib crypt32.lib user32.lib)
set_target_properties(OpenSSL::Crypto PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION ${OPENSSL_LIBCRYPTO_STATIC})
set_property(TARGET OpenSSL::Crypto
PROPERTY INTERFACE_LINK_LIBRARIES ${OPENSSL_LIBCRYPTO_DEPENDENCIES})
set(OPENSSL_LIBSSL_STATIC "${OPENSSL_LIBRARY_DIR}/libssl_static.lib")
set(OPENSSL_LIBSSL_DEPENDENCIES OpenSSL::Crypto)
set_target_properties(OpenSSL::SSL PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION ${OPENSSL_LIBSSL_STATIC})
set_property(TARGET OpenSSL::SSL
PROPERTY INTERFACE_LINK_LIBRARIES ${OPENSSL_LIBSSL_DEPENDENCIES})
# Directories and names compatible with CMake's FindOpenSSL.cmake
set(OPENSSL_CRYPTO_LIBRARY ${OPENSSL_LIBCRYPTO_STATIC})
set(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY} ${OPENSSL_LIBCRYPTO_DEPENDENCIES})
set(OPENSSL_SSL_LIBRARY ${OPENSSL_LIBSSL_STATIC})
set(OPENSSL_SSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_LIBSSL_DEPENDENCIES})
set(OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_LIBSSL_DEPENDENCIES} ${OPENSSL_LIBCRYPTO_DEPENDENCIES})
else()
add_library(OpenSSL::Crypto SHARED IMPORTED)
add_library(OpenSSL::SSL SHARED IMPORTED)
set(OPENSSL_LIBCRYPTO_SHARED "${OPENSSL_RUNTIME_DIR}/libcrypto-3-x64.dll")
set(OPENSSL_LIBCRYPTO_IMPORT "${OPENSSL_LIBRARY_DIR}/libcrypto.lib")
set(OPENSSL_LIBCRYPTO_DEPENDENCIES )
set_target_properties(OpenSSL::Crypto PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_IMPLIB ${OPENSSL_LIBCRYPTO_IMPORT}
IMPORTED_LOCATION ${OPENSSL_LIBCRYPTO_SHARED})
set_property(TARGET OpenSSL::Crypto
PROPERTY INTERFACE_LINK_LIBRARIES ${OPENSSL_LIBCRYPTO_DEPENDENCIES})
set(OPENSSL_LIBSSL_SHARED "${OPENSSL_RUNTIME_DIR}/libssl-3-x64.dll")
set(OPENSSL_LIBSSL_IMPORT "${OPENSSL_LIBRARY_DIR}/libssl.lib")
set(OPENSSL_LIBSSL_DEPENDENCIES OpenSSL::Crypto )
set_target_properties(OpenSSL::SSL PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_IMPLIB ${OPENSSL_LIBSSL_IMPORT}
IMPORTED_LOCATION ${OPENSSL_LIBSSL_SHARED})
set_property(TARGET OpenSSL::SSL
PROPERTY INTERFACE_LINK_LIBRARIES ${OPENSSL_LIBSSL_DEPENDENCIES})
# Directories and names compatible with CMake's FindOpenSSL.cmake
set(OPENSSL_CRYPTO_LIBRARY ${OPENSSL_LIBCRYPTO_IMPORT})
set(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY} ${OPENSSL_LIBCRYPTO_DEPENDENCIES})
set(OPENSSL_SSL_LIBRARY ${OPENSSL_LIBSSL_IMPORT})
set(OPENSSL_SSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_LIBSSL_DEPENDENCIES})
set(OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_LIBSSL_DEPENDENCIES} ${OPENSSL_LIBCRYPTO_DEPENDENCIES})
endif()
set_target_properties(OpenSSL::Crypto PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}")
set_target_properties(OpenSSL::SSL PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}")
add_library(OpenSSL::applink INTERFACE IMPORTED)
set_property(TARGET OpenSSL::applink PROPERTY
INTERFACE_SOURCES "${OPENSSL_APPLINK_SOURCE}")
endif()
unset(_ossl_prefix)
unset(_ossl_use_static_libs)
核心作用是为 CMake 项目导入 OpenSSL 的预编译库(静态 / 动态),并兼容 CMake 原生的FindOpenSSL.cmake接口。下面就来仔细说说OpenSSL的CMake引入文件。
2.核心功能概述
- 避免重复导入:检查 OpenSSL 的核心目标(
OpenSSL::Crypto/OpenSSL::SSL/OpenSSL::applink)是否已定义,防止重复配置或冲突; - 路径自动推导:从配置文件路径向上推导 OpenSSL 的安装根目录;
- 静态 / 动态库适配:根据
OPENSSL_USE_STATIC_LIBS变量选择静态 / 动态库; - 兼容 vcpkg 多配置:区分 vcpkg 的多配置(MODULE 模式)和单配置目标;
- 导入目标创建:定义标准的 OpenSSL CMake 导入目标,兼容原生 FindOpenSSL 接口。
3.细节分析
3.1.目标冲突检查(核心防重复逻辑)
cpp
# 预期的OpenSSL目标列表
set(_ossl_expected_targets OpenSSL::Crypto OpenSSL::SSL OpenSSL::applink)
# 遍历检查目标是否已定义
foreach(t IN LISTS _ossl_expected_targets)
if(TARGET "${t}")
LIST(APPEND _ossl_defined_targets "${t}")
else()
LIST(APPEND _ossl_undefined_targets "${t}")
endif()
endforeach()
# 全部已定义:直接返回(避免重复)
if(NOT _ossl_undefined_targets)
unset(...)
return()
endif()
# 部分已定义:报错(冲突无法调和)
if(_ossl_defined_targets)
message(FATAL_ERROR "Some targets defined, others not:...")
endif()
- 作用:防止多次调用
find_package(OpenSSL)或混合不同版本的 OpenSSL 配置导致目标冲突; - 关键:如果已有部分 OpenSSL 目标被定义,直接报错(避免混合静态 / 动态库、不同版本)。
这里涉及到一个指令**TARGET,它的语法如下:**
cpp
if(TARGET <target-name>)
# 目标存在时执行的逻辑
else()
# 目标不存在时执行的逻辑
endif()
- 返回
TRUE:目标已通过add_executable/add_library/find_package(导入)等方式创建; - 返回
FALSE:目标未创建,或名称拼写错误(含大小写)。
为什么要做这个判断?
- 避免重复创建目标 :若
find_package(OpenSSL)被多次调用,第一次会创建OpenSSL::Crypto等目标,第二次若不检查直接创建,会触发 CMake 报错("目标已存在"); - 防止混合配置冲突 :若部分目标已创建(如
OpenSSL::Crypto是静态库)、部分未创建(如OpenSSL::SSL),强行创建剩余目标会导致 "静态 / 动态库混用""版本不一致" 等链接错误; - 快速失败原则 :若检测到 "部分目标存在、部分不存在",直接报错(
message(FATAL_ERROR)),而非隐式兼容,避免后续更难排查的链接问题。
为什么要做 "全定义 / 全未定义" 检查?
- 若 "部分定义"(比如
OpenSSL::Crypto已定义,OpenSSL::SSL未定义),大概率是:- 多次调用
find_package(OpenSSL); - 混合了不同版本的 OpenSSL 配置(比如静态库和动态库混用);
- 手动创建过其中一个目标,与配置文件冲突。
- 多次调用
- 这种情况下继续配置会导致链接错误、版本不一致等问题,因此直接报错是最安全的选择。
3.2.根路径推导
cpp
get_filename_component(_ossl_prefix "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(_ossl_prefix "${_ossl_prefix}" PATH)
get_filename_component(_ossl_prefix "${_ossl_prefix}" PATH)
if(_ossl_prefix STREQUAL "/")
set(_ossl_prefix "")
endif()
- 逻辑:从当前配置文件路径向上退 3 级(对应 vcpkg 的
installed/<triplet>/share/openssl→ 根目录); - 示例:若配置文件路径是
D:/vcpkg/installed/x64-windows/share/openssl/OpenSSLConfig.cmake,则_ossl_prefix最终为D:/vcpkg/installed/x64-windows。
get_filename_component 是 CMake 中路径 / 文件名解析的核心内置指令,用于提取文件路径的任意组成部分(目录、文件名、扩展名、绝对路径等)。
3.3.静态 / 动态库选择
cpp
if(OPENSSL_USE_STATIC_LIBS)
set(_ossl_use_static_libs True)
elseif(DEFINED OPENSSL_USE_STATIC_LIBS)
# 显式禁用静态库但无动态库时,返回(让CMake找其他配置)
if(_ossl_use_static_libs)
return()
endif()
endif()
- 触发条件:
- 设
OPENSSL_USE_STATIC_LIBS=ON→ 使用静态库(libcrypto_static.lib/libssl_static.lib); - 未设置或设为
OFF→ 使用动态库(libcrypto.lib+libcrypto-3-x64.dll)。
- 设
3.4.版本 / 路径变量(兼容原生 FindOpenSSL)
cpp
# 版本变量(对接find_package(OpenSSL REQUIRED VERSION ...))
set(OPENSSL_VERSION "${OpenSSL_VERSION}")
set(OPENSSL_VERSION_MAJOR "${OpenSSL_VERSION_MAJOR}")
# 路径变量
set(OPENSSL_LIBRARY_DIR "${_ossl_prefix}/lib") # 库文件目录
set(OPENSSL_INCLUDE_DIR "${_ossl_prefix}/include") # 头文件目录
set(OPENSSL_RUNTIME_DIR "${_ossl_prefix}/bin") # DLL/可执行文件目录
set(OPENSSL_APPLINK_SOURCE "${_ossl_prefix}/include/openssl/applink.c") # Windows MSVC必需
- 关键:
applink.c是 Windows 下 MSVC 使用 OpenSSL 动态库的必要文件(解决 CRT 链接问题)。
在这里引入了OpenSSL的版本变量 OPENSSL_VERSION、OPENSSL_VERSION_MAJOR
OpenSSL的包含头文件目录、库文件目录等等。
3.5.vcpkg 多配置 / 单配置适配
cpp
if(NOT Z_VCPKG_OPENSSL_USE_SINGLE_CONFIG)
# 多配置:链式调用vcpkg的MODULE模式配置
set(Z_VCPKG_OPENSSL_USE_SINGLE_CONFIG "prevent-loop")
find_package(OpenSSL MODULE)
set(Z_VCPKG_OPENSSL_USE_SINGLE_CONFIG 0)
else()
# 单配置:创建原生导入目标
...
endif()
- 背景:vcpkg 支持 "多配置"(如 Debug/Release 共存)和 "单配置",这里通过标记变量避免循环调用。
3.6.静态库目标创建
cpp
add_library(OpenSSL::Crypto STATIC IMPORTED)
add_library(OpenSSL::SSL STATIC IMPORTED)
# 设置libcrypto静态库路径和依赖(Windows系统库)
set(OPENSSL_LIBCRYPTO_STATIC "${OPENSSL_LIBRARY_DIR}/libcrypto_static.lib")
set(OPENSSL_LIBCRYPTO_DEPENDENCIES ws2_32.lib gdi32.lib advapi32.lib crypt32.lib user32.lib)
set_target_properties(OpenSSL::Crypto PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION ${OPENSSL_LIBCRYPTO_STATIC})
set_property(TARGET OpenSSL::Crypto PROPERTY INTERFACE_LINK_LIBRARIES ${OPENSSL_LIBCRYPTO_DEPENDENCIES})
# libssl依赖libcrypto
set(OPENSSL_LIBSSL_STATIC "${OPENSSL_LIBRARY_DIR}/libssl_static.lib")
set(OPENSSL_LIBSSL_DEPENDENCIES OpenSSL::Crypto)
- 静态库依赖:
libcrypto需要链接 Windows 系统库(如ws2_32是网络库,crypt32是加密 API); - 接口传递:依赖会自动传递给链接
OpenSSL::SSL的目标。
IMPORTED_LINK_INTERFACE_LANGUAGES 是 CMake 中导入目标(IMPORTED Target) 的核心属性,直译是「导入的链接接口语言」,核心作用是告诉 CMake:这个预编译的导入库(如 OpenSSL 的静态 / 动态库)是用哪种 / 哪些编程语言编译的,CMake 会基于该信息处理链接阶段的语言适配、编译器标志传递等逻辑。
为什么必须设置?
以 OpenSSL 为例:libcrypto/libssl 是纯 C 编写 的库,如果不指定 IMPORTED_LINK_INTERFACE_LANGUAGES "C":
- CMake 可能会默认按当前目标的语言(比如 C++)处理,给链接阶段添加不必要的 C++ 标志(如
-lstdc++); - 极端场景下(如 C++ 目标链接纯 C 库),可能因语言标志冲突导致「未定义符号」(比如 C++ 的 name mangling 干扰 C 符号)。
3.7.动态库目标创建
cpp
add_library(OpenSSL::Crypto SHARED IMPORTED)
add_library(OpenSSL::SSL SHARED IMPORTED)
# 动态库需要指定DLL路径(IMPORTED_LOCATION)和导入库(IMPORTED_IMPLIB)
set_target_properties(OpenSSL::Crypto PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_IMPLIB ${OPENSSL_LIBCRYPTO_IMPORT} # .lib导入库
IMPORTED_LOCATION ${OPENSSL_LIBCRYPTO_SHARED}) # .dll运行时
- 关键区别:动态库需要同时指定
IMPORTED_IMPLIB(链接时用的.lib)和IMPORTED_LOCATION(运行时用的.dll)。
3.8.applink 目标(Windows 特有)
cpp
add_library(OpenSSL::applink INTERFACE IMPORTED)
set_property(TARGET OpenSSL::applink PROPERTY
INTERFACE_SOURCES "${OPENSSL_APPLINK_SOURCE}")
- 作用:MSVC 编译时,
applink.c会被自动包含,解决 OpenSSL 动态库与 CRT 的链接冲突。
4.使用方式
1.基础使用(vcpkg 环境)
cpp
# 安装OpenSSL(vcpkg)
# vcpkg install openssl:x64-windows (动态库)
# vcpkg install openssl:x64-windows-static (静态库)
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(openssl_demo)
# 启用静态库(可选)
# set(OPENSSL_USE_STATIC_LIBS ON)
# 查找OpenSSL
find_package(OpenSSL REQUIRED)
# 链接目标
add_executable(demo main.c)
target_link_libraries(demo PRIVATE OpenSSL::SSL OpenSSL::Crypto OpenSSL::applink)
2.非 vcpkg 环境
需确保:
- 配置文件路径正确,
_ossl_prefix能推导到 OpenSSL 安装根目录; - 静态 / 动态库文件存在于对应目录;
- Windows 系统库(如
ws2_32.lib)可被 CMake 找到。
5.常见问题与解决方案
1.报错 "Some targets defined, others not"
- 原因:多次调用
find_package(OpenSSL),或混合了不同版本的 OpenSSL 配置; - 解决:
- 确保只调用一次
find_package(OpenSSL); - 清除 CMake 缓存(
CMakeCache.txt)后重新配置; - 检查是否手动定义过
OpenSSL::Crypto等目标。
- 确保只调用一次
2.静态库链接时缺少系统库
- 现象:链接报错
unresolved external symbol(如WSAStartup); - 解决:确保代码中已链接
OpenSSL::Crypto(会自动传递系统库依赖),或手动链接:
cpp
target_link_libraries(demo PRIVATE ws2_32.lib advapi32.lib crypt32.lib)
3.动态库运行时缺少 DLL
- 现象:运行程序提示
libcrypto-3-x64.dll缺失; - 解决:
- 将
OPENSSL_RUNTIME_DIR(如vcpkg/installed/x64-windows/bin)加入系统 PATH; - 或复制 DLL 到程序输出目录:
- 将
cpp
add_custom_command(TARGET demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:OpenSSL::Crypto>
$<TARGET_FILE_DIR:demo>
)
add_custom_command(TARGET demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:OpenSSL::SSL>
$<TARGET_FILE_DIR:demo>
)
4.MSVC 编译报错 "applink.c missing"
- 原因:未链接
OpenSSL::applink目标; - 解决:在
target_link_libraries中加入OpenSSL::applink。
6.注意事项
- 平台限制 :该配置仅适用于 Windows(包含
openssl.exe、.dll、Windows 系统库依赖); - vcpkg 依赖 :代码中
Z_VCPKG_OPENSSL_USE_SINGLE_CONFIG是 vcpkg 特有标记,非 vcpkg 环境需移除相关逻辑; - 版本兼容 :适配 OpenSSL 3.x(库文件名
libcrypto-3-x64.dll),2.x 需调整库文件名; - CRT 一致性:静态库需确保与项目的 CRT(MD/MT)一致,否则会报链接错误。