前序篇章ios静态库
链接动态库
本地准备如下图所示 那么接下来使用clong来编译test.m 为目标文件
//可以不指定语言,会自动识别语言
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-I./AFNetworking \
-c test.m -o test.o
接下来链接AFNetworking
这个动态库
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-L./AFNetworking \
-lAFNetworking \
test.o -o test
此时已经生成了test
可执行文件,运行 ./test
,出现下面的错误
相信大家在使用动态库的时候,都碰到过类似的问题.
怎样生成一个动态库
本地准备如下
- 将test.m生成目标文件
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-I./dylib \
-c test.m -o test.o
- 将TestExample.m 生成目标文件
clang target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-c TestExample.m -o TestExample.o
- 生成
libTestExample.dylib
clang -dynamiclib \ \
> -target x86_64-apple-macos10.15 \
> -fobjc-arc \
> -isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
> TestExample.o -o libTestExample.dylib
test.o
链接libTestExample.dylib
clang -target x86_64-apple-macos10.15 \\
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-L./dylib \
-lTestExample \
test.o -o test
- 运行
test
可执行文件,之后报同样的错误
什么是动态库
动态库是一个链接编译的最终产物,而静态库只是目标文件的合集,那么就意味着静态库能够链接变成一个动态库。那接下来就先编译testExample成为一个静态库,然后链接成为一个动态库。
- TestExample.o 编译成为一个静态库
libtool \
-static -arch_only x86_64 \
TestExample.o -o libTestExample.a
- 链接
libTestExample.a
成为一个动态库,使用ld
ld -dylib -arch x86_64 \
> -macosx_version_min 10.15 \
> -syslibroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
> -lsystem -framework Foundation \
> libTestExample.a -o libTestExample.dylib
- 链接生成可执行文件
test
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-L./dylib \
-lTestExample \
test.o -o test
在生成可执行文件的时候,出现了下面的错误
那我们用objdump --macho -exports-trie libTestExample.dylib
命令查看是否有导出符号,输出如下
没有任何导出符号,原因是因为连接器默认的参数是-noall_load
,并没有加-all_load
的参数,导致链接的时候,认为没有用到的符号,就会被strip掉
ld -dylib -arch x86_64 -all_load \
-macosx_version_min 10.15 \
-syslibroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-lsystem -framework Foundation \
libTestExample.a -o libTestExample.dylib
再次查看导出符号表,输出如下
再次链接TestExample.dylib生成test可执行文件,没有报任何错误。
运行test还是报image not found
的错误。
TDB
tdb全称是text-based stub libraries
,本质上就是一个YAML描述的文本文件。他的作用是用于记录动态库的一些信息,包括到哦出的符号,动态库的架构信息,动态库的依赖信息。用于避免在真机开发过程中直接使用传统的dylib,对于真机来说,由于动态库都在设备上,在xcode上使用基于tbd格式的伪framework可以大大减少xcode的大小。
tdb 格式可以通过xcode打开查看里面的内容 当编译的时候链接一个库,只需要知道符号的位置就可以了,不需要知道源码。而在运行的时候,动态库是动态链接的,在运行的过程中是由dyld动态加载动态库的。
链接动态库过程
上述链接动态库之后,运行的时候一直报image not found
的错误,接下来就看下dyld 加载动态库的流程
在可执行文件中的mach-o header中保存这load 动态库的路径,当运行的时候可执行文件的时候,没有找到这个动态库的时候,就会报这个错误.
可以用otool -l test
查看test这个mach-o文件的内容
可以看到其中一个LC_LOAD_DYLIB
load一个动态库的路径为@rpath/TestExample.framework/TestExample
.
那我们查看下TestExample.framework这个里面的LC_ID_DYLIB
动态库(LC_ID_DYLIB
标记了动态库的访问路径)使用otool -l TestExample | grep 'ID' -A 5
来查看(-A/-B 5 向下/向上多显示5行)
那么LC_ID_DYLIB
怎么改动name
这个字段呢(改变动态库访问路径), 可以通过install_name_tool
来改变.
install_name_tool - change dynamic shared library install names
再次编译test.m
生成可执行文件,执行otool -l test
, 输出如下,
发现TestFramework的name已经变了。动态库保存路径,在可执行文件在链接的时候,将动态库的保存路径存到可执行文件的mach-o中。 当指定了正确的路径之后,再次运行test,没有任何输出错误。
那么我们能够生成这个动态库的时候,就指定保存路径吗?当然可以, 我们可以通过连接器参数来指定-install_name /Users/meilinli/Documents/动态库与framework/Frameworks/TestExample.framework/TestExample
clang -dynamiclib \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-install_name /Users/meilinli/Document/动态库与framework/Frameworks/TestExample.framework/TestExample \
TestExample.o -o TestExample
这个install_name 对应的就是xcode build 中的Dynamic Library install Name
但是-install_name
给定的是绝对路径,如果目录更改了,我们就得改编译代码,所以在此引@rpath.
@rpath
@rpath : Runpath seach Paths,dyld 搜索路径,运行时@rpath 指示dyld按顺序搜索路径列表,以找到动态库,@rpath 保存一个或多个路径的变量. @rpath 是由链接的主体提供的,比如A.framework是被B这个可执行文件链接的,那么@rpath就由B提供的。
对TestExample指定保存路径, 命令如下
install_name_tool -id @rpath/TestExample.framework/TestExample TestExample
再次查看 或者可以通过clang传递link参数
clang -dynamiclib \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample \
TestExample.o -o TestExample
-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample
指定动态库保存路径
那接下来test这个可执行文件需要提供@rpath, 通过之前的命令,链接生成test 可执行文件之后,查看test 可执行文件mach-o的load command 是否有rpath, 但是最终显示并没有rpath 这个字段,注意查找的时候是大小写敏感的, 查找RPATH
, 可以通过如下命令,在test mach-o 中添加rpath
install_name_tool -add_rpath /Users/meilinli/Documents/动态库 与framework/Frameworks test
但是add rpath 里面也是绝对路径,系统提供了以下两个path
- @executable_path: 表示可执行程序所在的目录,解析为可执行文件的绝对路径
- @loader_path: 表示被加载的
Mach-O
所在的目录,每次加载时,都可能被设置为不同的路径,由上层指定. 如果A.framework 被B.framework link,那么@loader_path 就指的是B所在的目录的绝对路径.
所以上面的命令可以替换成
install_name_tool -add_rpath @executable_path test
rpath 对应的build setting中Runpath Search Paths
在xconfig文件中的为LD_RUNPATH_SEARCH_PATHS
@loader_path
有如下业务场景,TestExample.framework 需要link TestExampleLog.framework
那么在编译生成TestExampleLog.framework
代码如下
clang -dynamiclib \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-Xlinker -install_name -Xlinker @rpath/TestExampleLog.framework/TestExampleLog \
TestExampleLog.o -o TestExampleLog
那么TestExample.framework
link TestExampleLog.framework
的时候则需要指定@rpath
为@loader_path/Frameworks
,
clang -dynamiclib \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-F./Frameworks \
-framework TestExampleLog \
-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample \
-Xlinker -rpath -Xlinker @loader_path/Frameworks \
TestExample.o -o TestExample