动态库
动态库定义
与静态库
相反,动态库
在编译时不会拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会真正的被加载进来。常见的格式有:.framework
、.dylib
、.tbd
。
缺点: 会导致一些性能损失,但是可以优化,比如延迟绑定(Lazy Binding
)技术。
编译链接动态库
之前已经尝试过制作链接静态库,那么今天将尝试链接动态库。
同样,现在将TestExample
编译链接称为动态库,test.m
变为目标文件后去链接这个动态库。
步骤:
- 编译
test.m
---->test.o
clang -target arm64-apple-macos12.0 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \-F./Frameworks \-I./dylib \-c test.m -o test.o
- 编译
TestExample.m
--->TestExample.o
clang -target arm64-apple-macos12.0 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \-c TestExample.m -o TestExample.o
-
编译
TestExample.m
--->libTestExample.dylib
这里有两种做法,一种还是使用
clang
命令,另一种是先编译为静态库
后再链接为动态库
clang
命令:
clang -dynamiclib \-target arm64-apple-macos12.0 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \TestExample.o -o libTestExample.dylib
- 先静态后动态:
libtool -static -arch_only arm64 TestExample.o -o libTestExample.a
ld -dylib -arch arm64 \-macosx_version_min 12.0 \-syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \-lsystem -framework Foundation \libTestExample.a -o libTestExample.dylib
-
链接
libTestExample.dylib
---->test EXEC
clang -target arm64-apple-macos12.0 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \-L./dylib \-lTestExample \test.o -o test
但是这里会报个错,链接过程符号找不到,这就是因为之前说的dead strip
,只需要加上-all_load
即可
但是在lldb
环境下运行已经生成的可执行文件时会报出另一个错
Image Not Found
原因分析
graph TD
dyld --> Mach-O --> LC_LOAD_DYLIB ---> /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
Mach-O --> LC_LOAD_DYLIB2 ---> libTestExample.dylib
大致如图所示,在链接动态库的过程中,其中路径便存在于可执行文件Mach-O
中的一个Load-Command
中,具体字段为LC_LOAD_DYLIB
,然后读到这个地址后会去以这个地址来加载动态库,如果没找到则报出image not found
具体可以使用命令otool -l test | grep "DYLIB" -A 5
来查看
同样这个路径是先存在于动态库本身的,通过命令查看otool -l libTestExample.dylib | grep "ID" -A 5
修改动态库安装路径
install_name_tool -id [绝对路径] libTestExample.dylib
这里同样可以使用链接器的命令来执行 ld -install_name [绝对路径]
再对可执行程序进行重新链接动态库后执行
此时再查看可执行程序中的LC_LOAD_DYLIB
的Load-Command
otool -l test | grep "DYLIB" -A 5
但是刚才使用的是绝对路径,一旦把库更换到别的目录,问题就出来了,非常不灵活
@rpath
@rpath
全名是:Runpath Search Paths
,按照我们的例子来讲就是Test
链接动态库,那么@rpath
就由Test
来提供。
同样@rpath
也是存在于可执行文件的LC_Command
中
install_name_tool -rpath [可执行文件所在路径]
otool -l test | grep "rpath" -A 5 -i
(-i
参数可忽略大小写进行搜索)查看,已存在LC_RPATH
此时再对动态库进行修改install_name_tool -change [之前路径] @rpath/dylib/libTestExample.dylib libTestExample.dylib
查看otool -l libTestExample.dylib | grep "ID" -A 5
这样即可成功运行
但是这样对于可执行文件test
来说,它提供的@rpath
其实还是一个绝对路径。
@executable_path
@executable_path
表示可执行程序所在的目录。
那么对于刚才的问题直接对test
的@rpath
进行修改即可
install_name_tool -rpath [原路径] @executable_path test
通过otool -l test | grep "rpath" -A 5 -i
查看
@loader_path
@loader_path
表示被加载的Mach-O
文件所在的目录。这样看起来好像和@executable_path
是一样的,这只针对于单层链接调用动态库是一样的情况,一旦可执行文件调用一个动态库A
,动态库A
又链接动态库B
,那么对于动态库B
给它提供@rpath
的就是动态库A
,动态库A
提供的RPATH
就可以使用到@loader_path
。
重新导出Framework
如下图所示,Test
链接调用动态库A
,动态库A
链接调用动态库B
graph TD
Test --> 动态库A --> 动态库B
那么Test
能直接调用动态库B
吗?答案是不行的,因为仅从符号的角度来看,通过命令objdump --macho --exports-trie [动态库A]
和命令nm -m [动态库A]
,导出符号表中没有动态库B
的符号。如果要调用可以使用重新导出动态库B
的方案。
在动态库A
编译链接动态库B
的过程中,告诉链接器要重新导出动态库B
-Xlinker -reexport_framework -Xlinker TestExampleLog
在动态库A
的Mach-O
中新增了LC_REEXPORT_DYLIB
字段,标记了重新导出的动态库
那么在Test
中编译成目标文件的过程中告诉其动态库B
的头文件存放路径 clang -target x86_64-apple-macos12.0 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \-I./Frameworks/TestExample.framework/Headers \-I./Frameworks/TestExample.framework/Frameworks/TestExampleLog.framework/Headers \-c test.m -o test.o
在Test.m
中直接调用动态库B
编译链接后成功运行,可以看到成功调用了动态库B