如何分离库的API与实现?共享库编译遇未解析符号问题
问题分析与解决方案
错误原因解析
当BUILD_SHARED_LIBS=ON时,example作为动态库,GenerateExportHeader生成的EXAMPLE_EXPORT宏,在未定义EXAMPLE_STATIC且未链接example的example-impl中会被解析为__declspec(dllimport)。这意味着example-impl会尝试从外部动态库导入Example的构造、析构函数等符号,但此时example-impl未链接example,导致链接器找不到这些符号。
静态库模式下无问题的原因是:静态库是目标文件的归档,最终链接可执行文件时,链接器会从两个静态库中提取所需符号并合并,无需提前显式链接。
解决方案一:明确链接依赖(合理且必要)
在共享库场景下,example-impl继承自Example,必须能访问基类的符号,显式链接是正确且必要的操作。
修改src/CMakeLists.txt,给example-impl添加对example的链接依赖:
add_library(example-impl example_impl.cpp example_impl.h) # 添加链接依赖 target_link_libraries(example-impl PRIVATE example) target_include_directories( example-impl PRIVATE "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>" "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>" ) target_compile_features(example-impl PRIVATE cxx_std_17)
此修改适配两种构建模式:
- 静态库模式:符号会被合并到最终目标文件
- 共享库模式:
example-impl会链接example动态库,正确导入基类符号
解决方案二:纯接口+工厂模式(彻底分离API与实现)
如果希望API库完全不包含任何实现代码,仅作为纯接口存在,可以采用这种模式:
1. 重构API为纯接口
修改include/example/example.h,让Example成为纯虚接口,并添加工厂函数声明(删除src/example.cpp):
#pragma once #include <example/export.h> namespace EX { class EXAMPLE_EXPORT Example { public: virtual ~Example() = default; // 将原非虚函数改为纯虚,公共逻辑移至实现类或工具类 virtual void func1() = 0; virtual void func2() = 0; }; // 工厂函数,由实现库提供具体实例 EXAMPLE_EXPORT Example* create_example(); }
2. 调整CMake配置
将API库改为接口库(仅提供头文件):
# 纯接口库,无实现代码 add_library(example INTERFACE) target_include_directories( example INTERFACE "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>" ) target_compile_features(example INTERFACE cxx_std_17) include(GenerateExportHeader) generate_export_header( example EXPORT_MACRO_NAME EXAMPLE_EXPORT EXPORT_FILE_NAME "${PROJECT_SOURCE_DIR}/include/example/export.h" STATIC_DEFINE EXAMPLE_STATIC ) add_library(example-impl example_impl.cpp example_impl.h) # 链接接口库 target_link_libraries(example-impl PRIVATE example) target_include_directories( example-impl PRIVATE "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>" "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>" ) target_compile_features(example-impl PRIVATE cxx_std_17)
3. 实现工厂函数与接口逻辑
在src/example_impl.cpp中补充实现:
#include "example_impl.h" namespace EX { ExampleImpl::ExampleImpl() {} ExampleImpl::~ExampleImpl() {} void ExampleImpl::func1() { /* 实现原公共逻辑 */ } void ExampleImpl::func2() { /* 实现特定逻辑 */ } Example* create_example() { return new ExampleImpl(); } }
这种模式的优势:
- API库
example完全无实现代码,仅定义接口规范 - 实现库
example-impl负责所有逻辑实现 - 用户只需链接
example-impl即可获取功能,无需关注API库的细节 - 天然适配静态/共享库两种模式,无链接符号问题
内容的提问来源于stack exchange,提问作者RafalMaziejuk
相关产品推荐
相关产品推荐

