在使用 GCC / G++ / MinGW 编译动态链接库的时候,我们常常会遇到需要控制导出符号的问题。比如,有时候我们想把一大堆依赖库塞进一个 .dll 文件里,这样就不会出现 .dll 依赖 .dll 的套娃现象,但是这样一来,编译器会自动把静态库里的所有函数都导出到 dll 里,然后动态链接库里就有了一大堆我们不需要的符号。
举个实际例子,我前段时间编译了 libass 的 DLL,它依赖 fontconfig,fontconfig 又依赖 expat,然后 libass 又依赖 libpng,libpng 依赖 zlib,导致最终生成的 DLL 里有一大堆 XML、zlib、png 和 FT_*
函数:
这个时候该怎么办呢?网上的文章大多会让你使用 gcc 的 -fvisibility=hidden
来控制符号的可见性。将所有其它符号设为不可见,就可以控制要导出的符号了。但是,实际编程中发现这样做需要大量修改源代码,加上 __declspec(dllexport)
定义。有没有更简单的方法呢?有,那就是今天要介绍的 version script。
version script 是 GNU 编译工具链中的一个非常有用的功能,它允许在编译的时候,使用文本文件来控制动态链接库中需要导出的符号,除此之外还有不少高级功能。version script 完整的规范定义在了 ld 工具的手册里(ftp.gnu.org/old-gnu/Man…),这里我们只是使用它控制导出符号的功能,因此只使用它的简化版。
下面我们就以一个实际例子来说明如何使用 version script 来控制符号导出。
这里我们以 libass 为例,首先,在 libass 的源代码根目录下新建一个文件,我们将它命名为 version-script.txt,内容如下:
{
global:
ass_library_init;
local:
*;
};
上述代码表示的意思是:
然后,我们在编译时,加上一个参数 -Wl,--version-script=version-script.txt
,这样就可以使用 version script 来控制符号的导出了。
检查一下输出的结果:
简单吧?以这种方法,我们就可以轻松地控制导出的符号,无需对源代码进行大量的修改。同时,version script 还有很多高级功能,包括给库指定不同版本等等。感兴趣的读者可以自行阅读 ld 工具手册以了解更多内容。美团也写过一篇关于优化 .so 动态链接库的文章,认为 version script 方式有一些额外的好处:
version script 方式可以控制编译进 so 的静态库的符号是否导出,visibility 和 attribute 方式都无法做到这一点。 visibility 结合 attribute 方式需要在源码中标明每个需要导出的符号,对于导出符号较多的项目来说是很繁杂的。version script 把需要导出的符号统一地放到了一起,能够直观方便地查看和修改,对导出符号较多的项目也非常友好。 version script 支持通配符, *
代表0个或者多个字符,?
代表单个字符。比如my*;
就代表所有以 my 开头的符号。有了通配符的支持,配置 version script 会更加方便。还有非常特殊的一点,version script 方式可以删除 __bss_start
这样的一些符号(这是链接器默认加上的符号)。
文中所使用的 DependenciesGui 工具可以在这里下载。