macos动态库加载分析

最近折腾的NPAPI插件需要用到第三方动态库, 总是加载不成功, 经过各种google, 终于明白:

##dylib_id 动态库标识 在java里, 加载jar库或者class文件都是已路径进行的, 而dylib也一样

每个dylib有一个id属性,这个值形式上是一个unix路径, 但其实它仅仅是一个id
这个id作为库的一个名称, 提供给连接者使用, 例如我有一个libimobiledevice.dylib 库:

//查看
liangmatoMacBook-Pro:MacOS liangwei$ otool -L ./libimobiledevice.dylib

./libimobiledevice.dylib:
        /usr/local/lib/libimobiledevice.3.dylib (compatibility version 4.0.0, current version 4.1.0)
        /usr/local/lib/libplist.1.1.8.dylib (compatibility version 1.0.0, current version 1.1.8)

第一行的/usr/local/lib/libimobiledevice.3.dylib 就是这个库的id, 不管这个dylib文件命名是什么, 也不管它放置在哪里, 当你的程序编译ld它的时候, 都是以id作为标识进行连接的
(当然, 一般情况下, 库安装路径跟id是相同的)
比如, 你有个程序A, 用到了libimobiledevice.dylib库, 库文件放在/workspace/test/libs下, 当你编译完之后, 查看A

liangmatoMacBook-Pro:MacOS liangwei$ otool -L ./A
    
./A:
        /usr/local/lib/libimobiledevice.3.dylib (compatibility version 4.0.0, current version 4.1.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)

因为A是个可执行程序, 所以, 第一行开始的内容就是使用到的库,
那么它使用的是以/usr/local/lib/libimobiledevice.3.dylib为id的库
这里的连接值是库的id, 而不是它的路径/workspace/test/libs/libimobiledevice.3.dylib

##DYLD_LIBRARY_PATH 动态库搜索路径 运行程序A, A使用了/usr/local/lib/libimobiledevice.3.dylib, 那么系统如何知道去哪里读取这个文件?
系统首先直接去/usr/local/lib/libimobiledevice.3.dylib路径去读取, 如果有则加载,
如果没有, 系统以文件名libimobiledevice.3.dylib去设定好的目录搜索的,默认的, 这个目录是/usr/lib.
你也可以进行设置, 添加其他一些搜索路径. 让系统在/usr/lib找不到之后再搜索这些目录
而这个设置, 正是DYLD_LIBRARY_PATH 环境变量:

export DYLD_LIBRARY_PATH=/另一个libs的存放路径/:/另一个libs的存放路径2/

系统搜索完默认的/usr/lib之后会依次搜索DYLD_LIBRARY_PATH指定的目录 如果把文件libimobiledevice.3.dylib改名, 会提示搜索不到, A将无法运行

##如何修改A所需库路径为程序所在目录下? 有一个变量叫@loader_path, 就是只可执行程序当前所在目录. 于是我们修改A对libimobiledevice.3.dylib的依赖

install_name_tool -change /usr/local/lib/libimobiledevice.3.dylib @loader_path/libimobiledevice.3.dylib A

再查看

liangmatoMacBook-Pro:MacOS liangwei$ otool -L ./A
        
./A:
        @loader_path/libimobiledevice.3.dylib (compatibility version 4.0.0, current version 4.1.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)

那么此时如果A在目录/workspace/test/A, 运行时, 会直接去/workspace/test/ 加载libimobiledevice.3.dylib

发现A还是无法运行, 发现libimobiledevice.3.dylib 依赖libplist.1.1.8.dylib ,
同样, 把libimobiledevice.3.dylib 对libplist.1.1.8.dylib的也修改为当前目录

install_name_tool -change /usr/local/lib/libplist.1.1.8.dylib @loader_path/libplist.1.1.8.dylib libimobiledevice.3.dylib

然后把libplist.1.1.8.dylib也拷贝到A所在目录下即可

##如何让A编译出来直接连接的是@loader_path下的库? 修改他们的id为@loader_path/xxx, 如:

install_name_tool -id @loader_path/libimobiledevice.3.dylib libimobiledevice.3.dylib

使用修改过的dylib文件进行重新编译, A直接就依赖@loader_path/libimobiledevice.3.dylib 而不再是/usr/local/lib/libimobiledevice.3.dylib

##其他

  1. loader_path 支持相对路径, 如:

    /workspace …/libs ……/libimobiledevice.3.dylib ……/libplist.1.1.8.dylib …/bin ……/A

那么A对libimobiledevice.3.dylib的依赖可以改为@loader_path/../libs/libimobiledevice.3.dylib

##完整例子

工程目录:

/workspace
A.c
.../libs
....../libimobiledevice.3.dylib
....../libplist.1.1.8.dylib
.../bin
....../A


A                               依赖 libimobiledevice.3.dylib, libusbmuxd.2.dylib, libplist.1.dylib    
libimobiledevice.3.dylib        依赖 libusbmuxd.2.dylib, libplist.1.dylib    
libusbmuxd.2.dylib              依赖 libplist.1.dylib    
libplist.1.dylib                无依赖    

1) 下载新的库
2) 分别修改 libimobiledevice.3.dylib, libusbmuxd.2.dylib, libplist.1.dylib 的id为 @loader_path/文件名
3) 分别修改 dylib对其他dylib的依赖为 @loader_path/文件名
4) Xcode 中Build Phases->Link Binary With Libraries, 添加libs目录下的三个库
5) Xcode 中Build Phases->Add Build Phase->Add Copy Files, 修改DestinationExecutables, 并添加libs目录下的三个库
6) 编译即可

进入bin目录查看得到:

liangmatoMacBook-Pro:libs liangwei$ otool -L /workspace/bin/*
/workspace/bin/A:
        @loader_path/libplist.1.dylib (compatibility version 1.0.0, current version 1.1.8)
        @loader_path/libusbmuxd.2.dylib (compatibility version 2.0.0, current version 1.0.8)
        @loader_path/libimobiledevice.dylib (compatibility version 4.0.0, current version 4.1.0)
        /System/Library/Frameworks/WebKit.framework/Versions/A/WebKit (compatibility version 1.0.0, current version 536.26.9)
        /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (compatibility version 1.0.0, current version 19.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)
/workspace/bin/libimobiledevice.dylib:
        @loader_path/libimobiledevice.dylib (compatibility version 4.0.0, current version 4.1.0)
        @loader_path/libplist.1.dylib (compatibility version 1.0.0, current version 1.1.8)
        @loader_path/libusbmuxd.2.dylib (compatibility version 2.0.0, current version 1.0.8)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 88.3.11)
        /usr/lib/libssl.0.9.7.dylib (compatibility version 0.9.7, current version 0.9.7)
        /usr/lib/libcrypto.0.9.7.dylib (compatibility version 0.9.7, current version 0.9.7)
        /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
/workspace/bin/libplist.1.dylib:
        @loader_path/libplist.1.dylib (compatibility version 1.0.0, current version 1.1.8)
        /usr/lib/libxml2.2.dylib (compatibility version 9.0.0, current version 9.16.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 88.3.11)
        /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
/workspace/bin/libusbmuxd.2.dylib:
        @loader_path/libusbmuxd.2.dylib (compatibility version 2.0.0, current version 1.0.8)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 88.3.11)
        @loader_path/libplist.1.dylib (compatibility version 1.0.0, current version 1.1.8)
        /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)