CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS

设置 CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 来减少 dllexport 的使用

Windows

windows 下函数的导出默认是内部的,需要使用额外的方法导出

#if defined(_MSC_VER) &&defined(_USRDLL)
 #ifdef API_EXPORT
    #define API __declspec(dllexport)
 #else
    #define API __declspec(dllimport)
 #endif
#endif

或者采用模块定义文件(.def),将需要导出的符号写到def文件中,格式如下:

EXPORTS FUNCTION
a
b
c
add
sub
mul
div

VS项目属性—》配置属性—》链接器—》输入—》模块定义文件中,配置def文件,在编译动态库时,就会将def中指定的符号导出。

第三种方式是导出所有符号:

根据 WIN32 (LD)的说法,链接器选项—​export-all-symbols关闭了自动导出功能,并导出除某些特殊符号之外的所有符号。因此,要在不修改C/C++代码的情况下导出bar_funcA,请在src/bar/CMakeLists.txt中添加以下行:

target_link_options(bar PRIVATE "-Wl,--export-all-symbols")

Linux下API符号导出方法

在Linux下动态库(.so)中,通过GCC的C visibility属性可以控制共享文件导出符号。在GCC 4.0及以上版本中,有个visibility属性,可见属性可以应用到函数、变量、模板以及C类。

限制符号可见性的原因:从动态库中尽可能少地输出符号是一个好的实践经验。输出一个受限制的符号会提高程序的模块性,并隐藏实现的细节。动态库装载和识别的符号越少,程序启动和运行的速度就越快。导出所有符号会减慢程序速度,并耗用大量内存。

  • default:用它定义的符号将被导出,动态库中的函数默认是可见的。

  • hidden:用它定义的符号将不被导出,并且不能从其它对象进行使用,动态库中的函数默认是被隐藏的。

default意味着该方法对其它模块是可见的。而hidden表面该函数/符号不会被放到动态符号表里,所以其它模块(可执行文件或者动态库)不可以通过符号表访问该方法。

要定义GNU属性,需要包含attribute和用括号括住的内容。可以将符号的可见性指定为visibility(“hidden”),这将不允许它们在库中被导出,但是可以在源文件之间共享。实际上,隐藏的符号将不会出现在动态符号表中,但是还被留在符号表中用于静态链接。

导出列表由编译器在创建共享库的时候自动生成,也可以由开发人员手工编写。导出列表的原理是显式地告诉编译器可以通过外部文件从对象文件导出的符号是哪些。GNU用户将此类外部文件称作为”导出映射”。

在linux中,动态库makefile中make cflag或ldflags配置为:CFLAGS+= -fvisibility=hidden 即默认动态库中的函数符号都是隐藏的。对于想要导出的函数符号,指定为:attributevisibility(“default”),在代码中指定:

#if  __GNC__ >=4
  #if defined(API_EXPORT)
  #define API_OUT  __attribute__((visibility("default")))
  #define API_LOCAL  __attribute__((visibility("hidden")))
  #elif defined(API_IMPORT)
  #define API  __attribute__((visibility("default")))
  #endif
#endif
Two notes when using this option from gcc: First, gcc doesn't know about this option,
           so you have to use -Wl,-whole-archive.  Second, don't forget to use
           -Wl,-no-whole-archive after your list of archives, because gcc will add its own list of
           archives to your link and you may not want this flag to affect those as well.

gcc -shared -o libmain.so main.o -Wl,--whole-archive gdiplus sqlite3 -Wl,--no-whole-archive

function linkStaticLib()
    -- A hack to be able to link StaticLib with whole-archive
    -- Should ideally just be links{"StaticLib"}, then it would just...work
    libdirs{"./config/build/staticlib"}
    dependson{"StaticLibProject"}

    -- GCC Options
    filter { "toolset:gcc", "configurations:Balacned" }
        linkoptions{"-Wl,--whole-archive libStaticLibBalanced.a -Wl,--no-whole-archive"}
    filter { "toolset:gcc", "configurations:Release" }
        linkoptions{"-Wl,--whole-archive libStaticLibRelease.a -Wl,--no-whole-archive"}
    filter { "toolset:gcc", "configurations:Debug" }
        linkoptions{"-Wl,--whole-archive libStaticLibDebug.a -Wl,--no-whole-archive"}
    filter { "toolset:gcc", "configurations:Profile" }
        linkoptions{"-Wl,--whole-archive libStaticLibProfile.a -Wl,--no-whole-archive"}

    -- Clang options
    filter { "toolset:clang", "configurations:Balanced" }
        linkoptions{"-Wl,-force-load,libStaticLibBalanced.a"}
    filter { "toolset:clang", "configurations:Release" }
        linkoptions{"-Wl,-force-load,libStaticLibRelease.a"}
    filter { "toolset:clang", "configurations:Debug" }
        linkoptions{"-Wl,-force-load,libStaticLibDebug.a"}
    filter { "toolset:clang", "configurations:Profile" }
        linkoptions{"-Wl,-force-load,libStaticLibProfile.a"}
    filter{}
end
set_property(TARGET foo PROPERTY INTERFACE_LINK_LIBRARIES -Wl,--whole-archive,$<TARGET_FILE:foo>,--no-whole-archive)

动态库链接路径

程序执行时的路径有三个来源:

  • 硬编码到程序中的路径

  • LD_LIBRARY_PATH 中列出的路径

  • 系统默认路径

当程序链接动态库时,动态库所在的路径就被硬编码到了程序中。例如:

  • /home/xxx/build/1.so → /home/xxx/build

  • libc.so → 运行时查找

  • ../1.so → ? 这个不知道

第二种情况是运行时查找,无需关注。对于第一种硬编码的路径,如果不想重新编译程序,可以直接修改二进制程序,将绝对路径直接更改即可。唯一需要注意的是修改后的路径和修改前的路径字符串长度相同。例如:

sed -i -e 's#/usr#././#g'

第二种方式是编译链接标志:

include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-Wl,-rpath CXX_Support_rpath)
if(${CXX_Support_rpath})
    add_link_options(-Wl,-rpath=./:./lib)
endif()

第三种是运行时直接修改 LD_LIBRARY_PATH:

export LD_LIBRARY_PATH=`pwd`
为了安全性考虑,Linux 运行运行时更改 LD_LIBRARY_PATH 对自己是无效的

动态库的优先级

  1. rpath:写死在程序中的绝对路径,在编译或者 configure 的时候一般有 -Wl,-rpath 选项

    修改 rpath 有几种方式: * 重新编译,在 ./configure 中配置 * 使用 chrpath 工具 * 使用 patchelf 工具 * 使用 sed

  2. LD_LIBRARY_PATH:环境变量

  3. ldconfig 的缓存:配置文件位于 /etc/ld.so.conf

  4. 默认的 /usr, /usr/lib 路径

弱符号

弱符号允许提供一个函数的默认实现。在程序加载的过程中,本地的符号会覆盖掉动态库中提供的默认实现,如果本地没有实现,则使用动态库中的实现。

对于 GCC 而言,可以使用 pragma 将一个符号的实现标记为弱符号:

#pragma weak alloctor_malloc

uint8_t *alloctor_malloc(uintptr_t size, uintptr_t align) {
    return (uint8_t *)malloc(size);
}
Last moify: 2022-12-04 15:11:33
Build time:2025-07-18 09:41:42
Powered By asphinx