场景一:链接动态库AFN
一、准备工作
准备一个test.m
文件,包含代码如下:
#import <Foundation/Foundation.h>
#import <AFNetworking.h>
int main(){
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSLog(@"testApp----%@", manager);
return 0;
}
二、指令操作
编译指令
clang -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -I ./AFNetworking -c test.m -o test.o
链接指令
clang -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -L./AFNetworking -lAFNetworking test.o -o test
注意:
- 与前文中静态库使用过的指令是相同的。
-
clang
可以自动识别语言
三、执行结果
- dyld: Library not loaded:XXX
- Referenced from:XXX
- Reason: image not found
四、原因??
自己生成一个动态库研究看看链接动态库原理。
场景二:链接动态库原理
一、准备工作
编写一个test.m
#import <Foundation/Foundation.h>
#import "TestExample.h"
int main(){
NSLog(@"testApp----");
TestExample *manager = [TestExample new];
[manager lg_test: nil];
return 0;
}
新建一个文件夹dylib
,里面包含两个文件TestExample.h
和TestExample.m
,TestExample.h
的代码如下
#import <Foundation/Foundation.h>
@interface TestExample : NSObject
- (void)lg_test:(_Nullable id)e;
@end
TestExample.m
的代码如下:
#import "TestExample.h"
@implementation TestExample
- (void)lg_test:(_Nullable id)e {
NSLog(@"TestExample----");
}
@end
二、编写脚本
敲指令过于麻烦,写一个脚本可以重复使用,用来控制编译链接整个过程。脚本如下:
echo "编译test.m --- test.o"
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-I./dylib \
-c test.m -o test.o
pushd ./dylib
echo "编译TestExample.m --- TestExample.o"
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-c TestExample.m -o TestExample.o
echo "编译TestExample.m --- libTestExample.dylib"
# -dynamiclib: 动态库
clang -dynamiclib -target x86_64-apple-macos10.15.7 -fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
TestExample.o -o libTestExample.dylib
popd
echo "链接libTestExample.dylib -- test EXEC"
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-L./dylib \
-lTestExample \
test.o -o test
将.o
编译成动态库
clang -dynamiclib -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk TestExample.o -o libTestExample.dylib
三、结果
还是出错,依然是那个image not found
错误。
四、思考
到底什么是动态库?
与静态库相反,动态库在编译时并不会被拷⻉到⽬标程序中,⽬标程序中只会存储指向动态库的引⽤。等到程序运⾏时,动态库才会被真正加载进来。
在上文中,.O
文件直接变成了一个静态库,静态库是一个.o
文件的集合。所以静态库可以看做.o
文件,是中间产物,我们可以用静态库去生成我们的可执行文件,或者是动态库。
五、将静态库链接为动态库
为了进一步研究,我们先将TestExample.o
生成静态库,再将静态库连接成动态库。在脚本中将生成动态库代码替换如下:
echo "编译TestExample.o --- libTestExample.a"
libtool -static -arch_only x86_64 TestExample.o -o libTestExample.a
echo "编译TestExample.m --- libTestExample.dylib"
ld -dylib -arch x86_64 \
-macosx_version_min 11.1 \
-syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-lsystem -framework Foundation \
libTestExample.a -o libTestExample.dylib
执行错误:未定义符号。在test.m
类中,使用了TestExample
类的lg_test
方法。未能找到动态库中的导出符号。
原因:在动态库在链接静态库时,由于静态库默认是-noall_load
,符合代码剥离条件,符号被剥离。
解决符号问题:在动态库链接静态库时,指定-all_load
参数。
修改后再重新执行脚本:
注意:静态库是
.o
文件的合集,动态库是.o
文件链接过后的> 产物。动态库是链接编译的最终产物,跟可执行文件是一个等级。
场景三:通过tdb文件加深动态库理解
场景二中生成动态库命令:
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-L./dylib \
-lTestExample \
test.o -o test
test.o在链接成为动态库的时候,到底用到了里面哪些东西?
一、准备工作
写好一个项目LoginAPP
,什么都没有的APP
。写好一个库叫SYCSSColor
,SYCSSColor
只有一个headers
和一个tdb
格式的文件。
使用SYCSSColor
这个tdb
格式的动态库,在LoginApp.debug.xcconfig
中设置HEADER_SEARCH_PATHS
。
直接将SYCSSColor.tbd
放到LoginAPP
的Frameworks文件夹下面。
在ViewController.m
中使用SYCSSColor
库。
#import <SYColor.h>
SYColor *color = [SYColor new];
不设置FRAMEWORK_SEARCH_PATHS
和OTHER_LDFLAGS
。没有设置在哪个路径下查找库,也没有设置库名称。
二、command+B
编译LoginAPP
项目
编译成功!!!
到底什么是tdb
格式?
tbd
:全称是text-based stub libraries
,本质上就是⼀个YAML
描述的⽂本⽂件(类似于json
文件,也就是配置文件)
tbd
格式的作⽤:
- ⽤于记录动态库的⼀些信息,包括导出的符号、动态库的架构信息、动态库的依赖信息
- ⽤于避免在真机开发过程中直接使⽤传统的
dylib
- 对于真机来说,由于系统动态库(例如
foundation
库等)都是在设备上,在Xcode
上使⽤基于tbd
格式的伪Framework
可以⼤⼤减少Xcode
的⼤⼩
原来在tdb
文件中包含了导出符号SYColor
。所以我们在编译时,能找到导出符号,编译就不会报错。
三、总结
我们通过clang XXX -L./dylib -lTestExample
去链接一个库的时候,只需要知道导出符号的位置就可以了,不需要源码就可以编译成功。
但是运行还是会报错image not found
,运行时DYLD
会动态加载动态库,找不到这个符号的真实地址。
-L
和-l
本质上是用来给动态库说明导出符号在什么地方。静态库在链接的时候,符号表已经合并在一起了,不需要这步操作。
场景四:构建一个动态库的Framework
不管动态库还是静态库,库里面结构都是一样的。Header
保存头文件;库文件:静态库是.a
,动态库是.dylib
。
一、准备工作
test.m
中main
方法调用TestExample
类的方法,各打印一条语句。
二、编写脚本
1、在TestExample.framework
目录下的脚本中编写如下内容:
echo "-------------编译TestExample.m to TestExample.o------------------"
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-I./Headers \
-c TestExample.m -o TestExample.o
echo "-------------TestExample.o to TestExample------------------"
clang -dynamiclib \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
TestExample.o -o TestExample
2、在test.m
所在的目录里面的脚本中编写如下内容:
echo "-------------编译test.m to test.o------------------"
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-I./Frameworks/TestExample.framework/Headers \
-c test.m -o test.o
echo "-------------将test.o链接成可执行文件------------------"
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-F./Frameworks \
-framework TestExample \
test.o -o test
三、执行结果
还是报image not found
错误
原因:为什么产生image not found?
这个问题要从DYLD
加载一个动态库说起。
- 当
dyld
加载一个Mach-O
时,在Mach-O
中会有一个名称为LC_LOAD_DYLIB
的Load Command
,它里面存储了动态库的路径 - 动态库是运行时加载的,它的加载方式就是
dyld
通过路径找到对应的动态库 - 如果路径有误,导致运行时
dyld
无法找到动态库,就会提示image not found
错误
查看Mach-O
中LC_LOAD_DYLIB
信息
使用otool -l test | grep 'DYLIB' -A 5
命令,查看Mach-O
中动态库的路径.。
我们发现有完整的路径/System/Library/Frameworks/
和/usr/lib/libSystem
,这些库都可以正常加载。也存在路径有问题的动态库,也就是图中标注的,这正是导致image not found的原因。
-
@rpath
未知导致路径无效,无法找到动态库。 - 在场景四
test
的自定义的TestExample
动态库,它的路径只有一个TestExample
名称,相当于和Mach-O
平级,在运行时无法找到动态库。
解决image not found
问题产生的原因是路径不对,说明需要在动态库中,将动态库自身路径补充完整。
在什么时候补充?在动态库产生的时候,也就是将.o
文件链接成动态库时。
动态库是链接时不会复制,程序运行时由系统动态加载到内存。所以在可执行文件链接动态库时,动态库并未在可执行文件中,只是一个包含地址的配置文件。
解决办法一:给现有动态库添加路径
使用install_name_tool
命令给动态库添加路径。改变动态库的install name
,相当于所在路径。使用-id (name)
参数,改变动态库所在路径。
在场景一中添加绝对路径
install_name_tool -id /Users/chenshuangchao/Desktop/完成链接动态库AFN/AFNetworking/libAFNetworking.dylib libAFNetworking.dylib
在场景二中添加相对路径@rpath
第一步、给动态库添加install_name
install_name_tool -id @rpath/dylib/libTestExample.dylib libTestExample.dylib
第二步、给可执行文件添加@rpath
,使用install_name_tool -add_rpath
。
install_name_tool -add_rpath /Users/chenshuangchao/Desktop/完成动态库原理 test
在场景四中@rpath
也使用相对路径
@executable_path
:表示可执⾏程序所在的⽬录,解析为可执⾏⽂件的绝对路径。@loader_path
:表示被加载的Mach-O
所在的⽬录。每次加载时,都可能被设置为不同的路径,由上层指定。
在TestExample
的build.sh
中修改如下参数,在生成动态库时,传递一个install_name
-Xlinker -install_name -Xlinker @rpath/Frameworks/TestExample.framework/TestExample \
在生成test
可执行文件的脚本中,添加如下参数,在生成test
可执行文件时,添加rpath
-Xlinker -rpath -Xlinker @loader_path \
解决办法二:生成时给动态库添加路径
- 链接成为动态库时,指定相对路径。使用ld命令的
-install_name
参数,在链接成为动态库时,指定所在路径。 - 在生成可执行文件时,设置
@rpath
路径。谁链接动态库,谁提供@rpath
重点:@rpath
绝对路径无法通用,项目移动后就无法找到。可执行文件和动态库之间约定了一个规则,可执行文件提供路径@rpath
,动态库提供相对于可执行文件的路径。
-
Runpath search Paths
:dyld搜索路径,谁需要链接动态库谁就需要提供@rpath
。 - 运行时
@rpath
指示dyld按顺序搜索路径列表,以找到动态库。 -
@rpath
保存一个或 多个路径的变量。
动态库自己指定相对路径
在clang
中指定-install_name
,加上如下参数:
-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample
在ld
中添加-install_name
,无须添加-Xlinker
:
-install_name @rpath/TestExample.framework/