动态链接库搜索

从比较清楚的 Linux 说起。

Linux 有自己的动态链接程序 ld.so,动态链接的程序会载入这个动态链接库,它负责完成找到需要的其他动态链接库。比较推荐的做法是链接的时候加入 -rpath 参数指明所谓的 RUNPATH,这样可执行文件(或者依赖其他动态链接库的动态链接库)就能告诉 ld.so 到哪里去搜索对应的动态链接库了。对于没有设定 RPATH 的可执行文件或者动态链接库,我们可以用 LD_LIBRARY_PATH 这个环境变量通知 ld.so,这个方法的问题是,环境变量有时候设置了(如果 export 的话)会导致其他不需要设置的程序载入了不应该载入的动态链接库,不 export 就得每次先设定再执行。通过 LD_PRELOAD 我们可以要求 ld.so 预先载入某些动态链接库,这样就能 override 系统的动态链接库(比如使用特殊版本的 libc.so)。

更多的环境变量可以 man ld.so,察看一个文件依赖的动态链接库使用 ldd,而修改一个文件的 RPATH 可以通过 chrpath 命令。值得注意的是 RPATH 本身也可以通过环境变量 RUNPATH 进行覆盖。

再谈 Mac OS,这部分主要参考这篇 blog这篇。Mac 推自己那套机制比较久了,加上原来是 BSD 系的,和 Linux 有一定的区别。它的 dynamic linker 是 dyld 这个程序,动态链接库的载入是依靠它完成解析之类的事情,它支持三个 @xxx_path 用来支持一些比较奇怪的用途:

  • @executable_path,dyld 会将它替换成为可执行文件所在的路径,因为现在 Mac 不少程序都使用 app 那种封装(xxx.app 目录里面有自己独立的 dylib 之类的),为了找到这些 dylib,可以通过这个变量加上相对路径获得。
  • @loader_path 主要是为了 plugin 能都定位自己需要的 framework,因为 plugin 本身也是动态链接库,其安装位置也不见得是和可执行文件打包在一起(如浏览器的 plugin,它一般被多个浏览器共享),通过这个变量加上相对路径就能定位这个 plugin 需要的动态链接库了。
  • @rpath 这个是继承了原先的 RPATH 的概念,通过 linker 设定 -rpath 参数,这会对应一个搜索的列表,而这甚至可以包含前面两个变量,这样就可以一个一个搜索需要的 dylib。

更多的细节可以 man dyld,察看一个文件依赖的动态链接库使用 otool -L,修改这些动态链接库的位置可以用 install_name_tool -change。像开发阶段往往直接链接到本地的 dylib,此时对应的文件里面的路径一般是绝对的,我们可以在发布程序的时候替换成相对的。一个有用的策略是编译动态链接库时:如果是 share 到任意程序的的通过 -install_name 插入绝对路径;否则根据情况(比如是 app bundle 自己用还是 plugin)设定为对应变量的相对位置;这个 install name 保证其他文件动态连接它时插入的名字是这个给定的字符串(install name 可以用 install_name_tool -id 来修改);它依赖的动态链接库使用 -rpath 指定,这样就算连接的动态链接库位置不是系统路径,也能自动找到。比如我们写了一个 liba.dylib,为了很多其他程序方便用它,如果是类似 Linux 的做法,install name 可能是 /usr/local/lib/liba.dylib,这样我的程序 a-driver 直接写 -L/usr/local/lib -la 就能正确的找到我安装在 /usr/local/lib 下的动态链接库了。但是很可能 a-driver 在写的时候 liba 也没写完,那么我可能编译 liba 时写成 @rpath/liba.dylib 更合适,因为我的 a-driver 通过修改 -rpath 就可以测试我正在写的 liba 或者是 deploy 到 /usr/local/lib 下的两个版本了(尽管我仍然是 -L some-dir -la 链接 a-driver 的,这里的 some-dir 可以是 /usr/local/lib 也可以是我开发环境下的目录)。倘若是某一个 app bundle 里面的 liba.dylib 我们很可能就写成 @executable_path/../Resources/liba.dylib 了。

这里的 RPATH 也支持环境变量 LC_RPATH;dyld 也支持 DYLD_LIBRARY_PATH。像 readelf 可以看到一个 ELF 的 RPATH 设置,mac 下面似乎只能 objdump -x 然后在里面搜索了。

根据这个原理,Qt 提供的 macdeployqt 干的事情就是根据一个可执行文件和依赖的 dylib/framework 修改对应的路径为某些变量搞定的。

为了完整性,我们贴个 Windows 下面动态链接库搜索的文章,毕竟很多年没碰 Windows 开发了。

——————
And the men said to Lot, Have you here any besides? son in law, and your sons, and your daughters, and whatever you have in the city, bring them out of this place:

Advertisements
动态链接库搜索

SWIG 初体验

想来在这个 scripting language 盛行的时代很多人都不大会选择 C/C++ 作为首选的开发语言,其中一个问题在于尽管 C/C++ 的执行效率很高,却不拥有 scripting language 里面那些最让人觉得好用的东西,比如不严格的类型检查、garbage collection 等等,另外这些语言的解释器比起 C/C++ 的写程序、编译、测试的过程更加便捷(在 interpreter 的命令行上面玩玩多方便,瞧瞧人家 matlab)。

可是很多脚本语言还是需要有 C/C++ 模块的支持的,所谓的模块无非就是对某些 C/C++ 类或者函数的 wrapper,使得原来的一些函数能与脚本语言的解释器进行交互,因此本质上所谓的模块就是一个解释器能够动态载入的动态链接库。我们知道 Matlab 的 mex 就是干这个事情,我们也知道,每个 mex 的入口函数都是 mexFunction。那么千千万万的脚本语言的接口我们都要去了解是不是过于沉重的负担了呢?SWIG 的出现一定程度的缓解了这种开销。

SWIG 的设计就是通过 swig 命令进行预处理,用一个模块说明文件(一般是 .i 后缀)产生 wrapper,和对应的 C/C++ 程序一起编译连接成为动态链接库。(注:这部分代码主要来自 SWIG 的 manual)我们看看如下的一个接口声明文件:

/* File : example.i */
%module example
%{
/* Put headers and other declarations here */
extern double My_variable;
extern int    fact(int);
extern int    my_mod(int n, int m);
%}

extern double My_variable;
extern int    fact(int);
extern int    my_mod(int n, int m);

这里声明了一个 double 和两个函数,这两个函数非常简单啦,如下

/* File : example.c */

double  My_variable  = 3.0;

/* Compute factorial of n */
int  fact(int n) {
	if (n <= 1) return 1;
	else return n*fact(n-1);
}

/* Compute n mod m */
int my_mod(int n, int m) {
	return(n % m);
}

然后有如下的 Makefile:

CFLAGS += -I/usr/lib/perl/5.10/CORE/
CC = /usr/bin/gcc-4.1

example_wrap.c: example.pm

example.pm: example.i
	swig -perl $^

example.so: example.o example_wrap.o
	gcc -shared $^ -o$@

.PHONY: clean

clean:
	rm -f *.o *.pm *~

我们 make example.so 后,可以用如下的 perl 程序测试:

#!/usr/bin/perl -w

use strict ;
use example ;

print example::fact(4), "\n";
print example::my_mod(23,7), "\n";
print $example::My_variable + 4.5, "\n";

我们来看看上面这些东西怎么组合在一起的:首先看 Makefile,这揭示了几个文件的依赖关系,我们知道模块由原来的那个 C/C++ 源程序和一个 wrapper 组成(即 example_wrap.c),这个接口文件是 SWIG 生成的,然后 SWIG 还生成了一个 .pm 文件,这是为了利用脚本语言的特性(即 perl 的 use 或者 require 读入一个模块)。然后我们在 perl 程序里面非常 perl 的使用了这些东西。

后面有空的话将详细的研究一下 perl 的各个数据结构和 C/C++ 的转换方法。这样你有什么 C/C++ 的库,你就能用 perl 使用它们了。记得很多语言的 binding 么,比如 gtk、Qt 等在 python 里面的 binding,现在大约明白我们怎么将这些 GUI kits 用在 scripting language 里面了。google 一下,看看 Matlab 有没有 SWIG 的支持,ms 是有的!

PS: debian 里面的那个 perl 的 CORE 似乎跟 gcc 4.1 以后的有问题,在一台 solaris 上 gcc 4.0 没问题,但是我在 debian 用 gcc-4.1 就不行,汗…

——————
My day is done, and I am like a boat drawn on the beach, listening to the dance-music of the tide in the evening.

SWIG 初体验