在静态库原理一文中,我们介绍了静态的原理,在这里我们通过编译器clang
命令,手动来制作动态库,并探索其原理。
链接动态库AFN
我们新建一个test.m
文件,并使用 AFN
动态库
#import <Foundation/Foundation.h>
#import <AFNetworking.h>
int main(){
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSLog(@"链接AFN----%@", manager);
return 0;
}
AFN
和test.m
的目录结构如下
将 test.m 编译为 test.o
打开终端,我们进入代码目录,输入一下命令
bel@beldeMacBook-Pro 链接动态库AFN % clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
-I./AFNetworking \
-c test.m -o test.o
- -target: 指定架构。
- -fobjc-arc: ARC环境。
- -isysroot: 编译的SDK路径。
- -I :指定头文件位置, 对应Xcode中的 header search path。
- -c xxx.m -o xxx.o: 将.m文件编译为 .o 文件。
这样我们就将
test.m
编译为了test.o
链接test.o得到可执行文件
将 test.o 链接成可执行文件
bel@beldeMacBook-Pro 链接动态库AFN % clang \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
-L./AFNetworking \
-lAFNetworking \
test.o -o test
- -L< dir > : 指定库文件路径(.a.dylib库文件),
对应Xcode中的Library search Path
- -l< libarayr_name>:指定链接的库文件名称(.a.dylib库文件)。
对应Xcode中的 other link flags
到这里我们就得到了可执行文件test
。
加载运行
我们使用 lldb -file
命令来加载运行test
文件,加载成功,输入 r
既可以运行
bel@beldeMacBook-Pro 链接动态库AFN % lldb -file test
(lldb) target create "test"
Current executable set to '/Users/bel/Desktop/动态库原理/链接动态库AFN/test' (x86_64).
(lldb) r
Process 40118 launched: '/Users/bel/Desktop/动态库原理/链接动态库AFN/test' (x86_64)
dyld: Library not loaded: @rpath/AFNetworking.framework/Versions/A/AFNetworking
Referenced from: /Users/bel/Desktop/动态库原理/链接动态库AFN/test
Reason: image not found
Process 40118 stopped
* thread #1, stop reason = signal SIGABRT
frame #0: 0x000000010006bf7a dyld`__abort_with_payload + 10
dyld`__abort_with_payload:
-> 0x10006bf7a <+10>: jae 0x10006bf84 ; <+20>
0x10006bf7c <+12>: movq %rax, %rdi
0x10006bf7f <+15>: jmp 0x10006a160 ; cerror_nocancel
0x10006bf84 <+20>: retq
Target 0: (test) stopped.
(lldb)
What!!!!!!
在运行时报错了,原因为dyld: Library not loaded
,为什么会这样呢?这里也卖个关子,稍后进行在进行解答。
制作动态库
我们使用编译器命令来制作动态库并进行链接。首先新建dylib
文件夹,里面只用一个.m
和.h
文件,只执行了一个NSLog
方法,其目录结构如下
编译.m 文件
1,将主程序test.m 编译为 test.o
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
-I./dylib \
-c test.m -o test.o
将动态库.m文件编译为 .dylib
clang -dynamiclib \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
TestExample.m -o libTestExample.dylib
此时,我们的动态库已经制作完成
我们通过file
命令来查看libTestExample.dylib
属性,它是 dynamically linked shared library
bel@beldeMacBook-Pro dylib % file libTestExample.dylib
libTestExample.dylib: Mach-O 64-bit dynamically linked shared library x86_64
动态库
是编译链接后的最终产物。和静态库
不同的是静态库是.o文件的合集,
链接动态库,生成可执行文件
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
-L./dylib \
-lTestExample \
test.o -o test
这样我们的主程序test
就将动态库TestExample
链接完成,我们使用lldb lldb -file test
进行加载运行,同样,我们又碰到了dyld: Library not loaded: libTestExample.dylib Referenced from: /Users/bel/Desktop/动态库原理/制作动态库/test Reason: image not found
错误。
dyld加载可执行文件
LC_LOAD_DYLIB
当运行程序时,dyld
会根据cmd
等于LC_LOAD_DYLIB
的Load Command
去加载动态库,我们使用otool
来查看test
可执行文件中的DYLIB
命令
bel@beldeMacBook-Pro 制作动态库 % otool -l test | grep 'DYLIB' -A 2
cmd LC_LOAD_DYLIB
cmdsize 48
name libTestExample.dylib (offset 24)
--
cmd LC_LOAD_DYLIB
cmdsize 96
name /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (offset 24)
--
cmd LC_LOAD_DYLIB
cmdsize 56
name /usr/lib/libobjc.A.dylib (offset 24)
--
cmd LC_LOAD_DYLIB
cmdsize 56
name /usr/lib/libSystem.B.dylib (offset 24)
--
cmd LC_LOAD_DYLIB
cmdsize 104
name /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (offset 24)
-l
: The objdump(1) option to display the load commands,查找MachO中的load commandsgrep 'DYLIB' -A 2
: 从结果中筛选出包含DYLIB
的信息,并向下多展示2行
从结果我们可以看出:
- 1,test程序共使用了 5 个动态库。
- 2,
libTestExample.dylib
的name
路径,不是其所在位置的真实路径。 根据以上分析我们可知,dyld: Library not loaded: libTestExample.dylib
的错误,是由name
路径没有指向动态库的真实路径,导致在运行时,找不到动态库。
LC_ID_DYLIB
动态库的路径是保存在动态库自己的Mach-O里面的
,动态库的路径是由 LC_ID_DYLIB
提供的, 我们来查看下libTestExample.dylib
的 LC_ID_DYLIB
。
bel@beldeMacBook-Pro dylib % otool -l libTestExample.dylib | grep 'ID' -A 2
cmd LC_ID_DYLIB
cmdsize 48
name libTestExample.dylib (offset 24)
--
cmd LC_UUID
cmdsize 24
uuid 9DDBE61D-71C2-3E5A-A6CA-65119022065E
这说明,我们在链接生成动态库的时候,路径给的是错误的。
install_name_tool
既然我们在链接的时候给的路径是错误的,那有没有办法去更改路径呢。我们可以使用install_name_tool
去修改LC_ID_DYLIB
的name
值。 使用man install_name_tool
可以查看其用途,主要是用来更改动态库的安装路径的。
我们将真实的动态库路径写入libTestExample.dylib
的name
值里面:
install_name_tool -id /Users/bel/Desktop/动态库原理/制作动态库/dylib/libTestExample.dylib libTestExample.dylib
bel@beldeMacBook-Pro dylib % otool -l libTestExample.dylib | grep 'ID' -A 2
cmd LC_ID_DYLIB
cmdsize 104
name /Users/bel/Desktop/动态库原理/制作动态库/dylib/libTestExample.dylib (offset 24)
--
cmd LC_UUID
cmdsize 24
uuid 9DDBE61D-71C2-3E5A-A6CA-65119022065E
我们已经将路径值写入到了动态库的name
值里面。返回到上一目录,我们重新进行链接:
bel@beldeMacBook-Pro 制作动态库 % clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
-L./dylib \
-lTestExample \
test.o -o test
然后加载运行
bel@beldeMacBook-Pro 制作动态库 % lldb -file test
(lldb) target create "test"
Current executable set to '/Users/bel/Desktop/动态库原理/制作动态库/test' (x86_64).
(lldb) r
Process 40895 launched: '/Users/bel/Desktop/动态库原理/制作动态库/test' (x86_64)
2021-07-04 18:12:34.400920+0800 test[40895:3209920] testApp----
2021-07-04 18:12:34.401236+0800 test[40895:3209920] TestExample----
Process 40895 exited with status = 0 (0x00000000)
在这里已经成功运行了!!!!!!
RPATH
我们在动态库中,我们将name值写入了一个绝对路径,但如果我们在其他工程使用,该路径将会报错。那有没有一种通用的路径设置呢 ? 苹果给我们提供了@rpath
来解决该问题。
@rpath
RPATH
全称 Runpath search Paths
,是dyld
的搜索路径。在运行时@rpath
指示dyld
按顺序搜索路径列表,以找到动态库。
我们将libTestExample.dylib
的name值用@rpath
来替代
bel@beldeMacBook-Pro dylib % install_name_tool -id @rpath/dylib/libTestExample.dylib libTestExample.dylib
bel@beldeMacBook-Pro dylib % otool -l libTestExample.dylib | grep 'ID' -A 2
cmd LC_ID_DYLIB
cmdsize 64
name @rpath/dylib/libTestExample.dylib (offset 24)
--
cmd LC_UUID
cmdsize 24
uuid 9DDBE61D-71C2-3E5A-A6CA-65119022065E
谁链接动态库,谁提供RPATH值
,在这里需要程序test
提供RPATH
值
bel@beldeMacBook-Pro 制作动态库 % install_name_tool -add_rpath /Users/bel/Desktop/动态库原理/制作动态库 test
bel@beldeMacBook-Pro 制作动态库 % otool -l test | grep 'RPATH' -A 5
cmd LC_RPATH
cmdsize 64
path /Users/bel/Desktop/动态库原理/制作动态库 (offset 12)
bel@beldeMacBook-Pro 制作动态库 %
test
文件中的LC_RPATH
的值就变成了我们写入的路径
我们不需要在次进行链接,直接运行
bel@beldeMacBook-Pro 制作动态库 % lldb -file test
(lldb) target create "test"
Current executable set to '/Users/bel/Desktop/动态库原理/制作动态库/test' (x86_64).
(lldb) r
Process 40955 launched: '/Users/bel/Desktop/动态库原理/制作动态库/test' (x86_64)
2021-07-04 18:34:01.233540+0800 test[40955:3221046] testApp----
2021-07-04 18:34:01.233942+0800 test[40955:3221046] TestExample----
Process 40955 exited with status = 0 (0x00000000)
(lldb)
@executable_path
此时,我们已经注意到,在test
的LC_RPATH
中,路径是我们写死的一个路径,在使用过程中会造成很多错误,我们可以使用@executable_path
变量来替代。
@executable_path
:表示可执行程序所在的目录,解析为可执行文件的绝对路径
bel@beldeMacBook-Pro 制作动态库 % install_name_tool -rpath /Users/bel/Desktop/动态库原理/制作动态库 @executable_path test
bel@beldeMacBook-Pro 制作动态库 % otool -l test | grep 'RPATH' -A 5
cmd LC_RPATH
cmdsize 32
path @executable_path (offset 12)
- 1,由于之前我们已经写入了
RPATH
值,所以,我们使用rpath
参数来进行修改RPATH
值。将原来的值替换为@executable_path
。
我们直接运行
bel@beldeMacBook-Pro 制作动态库 % lldb -file test
(lldb) target create "test"
Current executable set to '/Users/bel/Desktop/动态库原理/制作动态库/test' (x86_64).
(lldb) r
Process 40984 launched: '/Users/bel/Desktop/动态库原理/制作动态库/test' (x86_64)
2021-07-04 18:48:49.994214+0800 test[40984:3227228] testApp----
2021-07-04 18:48:49.994511+0800 test[40984:3227228] TestExample----
Process 40984 exited with status = 0 (0x00000000)
(lldb)
在 Xcode
的项目中 在 Build Settings
里面也可以找到 RPATH
变量的设置
编译器写入
在上面的过程中,我们使用的是install_name_tool
工具来对Mach-O
进行修改,我们也可以使用链接器的 -Xlinker
参数来修改 动态库的LC_ID_DYLIB
值和可执行程序LC_RPATH
。
-Xlinker -install_name -Xlinker @rpath/dylib/libTestExample.dylib // 1
-Xlinker -rpath -Xlinker @executable_path // 2
- 1,修改动态库的
LC_ID_DYLIB
值。 - 2,修改可执行程序的
LC_RPATH
值。
我们可以将以上步骤写在Shell脚本
里面,就会非常的便捷。
echo "编译test.m --- test.o"
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
-I./dylib \
-c test.m -o test.o
#-I 指定头文件所处的位置
echo "编译 TestExample.m --- libTestExample.dylib"
# -dynamiclib:动态库
pushd ./dylib
clang -dynamiclib \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
-Xlinker -install_name -Xlinker @rpath/dylib/libTestExample.dylib \
TestExample.m -o libTestExample.dylib
# -Xlinker -install_name -Xlinker @rpath/libTestExample.dylib: 将 name 定义为 @rpath/libTestExample.dylib
echo "-------LC_ID_DYLIB---------"
otool -l libTestExample.dylib | grep 'LC_ID_DYLIB' -A 3
popd
#-L 库文件的位置
#-l 库文件的名称
echo "链接libTestExample.dylib -- test EXEC"
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
-Xlinker -rpath -Xlinker @executable_path \
-L./dylib \
-lTestExample \
test.o -o test
echo "-------LC_RPATH---------"
otool -l test | grep 'LC_RPATH' -A 3
# -Xlinker -rpath -Xlinker @executable_path \ 将rpath定义为 @executable_path
总结:
- 1,动态库是编译链接后的最终产物,静态库是.o文件的合集。
- 2,程序运行时,
dyld
加载动态库是按照Mach-O中LC_LOAD_DYLIB
去加载动态库的。name
值是动态库的路径。 - 3,
可执行文件
通过LC_RPATH
Load Command 提供@rpath
的值,用@executable_path
变量表示可执行程序所在的目录。 - 4,
动态库
通过LC_ID_DYLIB
Load Comand 使用@rpath
值,和 动态库的相对路径,来表示动态库所在的位置。
最后,我将本文探索的两个实例和编译脚本buildDynamic.sh
,存放到了我的仓库(动态库原理),有兴趣的可以去下载。