动态库
动态库定义
与静态库相反,动态库在编译时不会拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会真正的被加载进来。常见的格式有:.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.ald -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