iOS小知识点

10、iOS强化 --- 动态库

2021-03-13  本文已影响0人  Jax_YD

接下来我们一起来探索一下动态库

动态库原理

首先看一下我们的测试环境:

image
build里面的指令我们在9、iOS强化 --- 静态库里面都有讲过,不同的是将TestExample.o --->TestExample.a 换成了 TestExample.o --->TestExample.dylib
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.o ---> libTestExample.dylib"
# -dynamiclib: 动态库
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 libTestExample.dylib

popd

echo "链接libTestExample.dylit --- 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

我们第一次执行脚本的时候,同样会遇到build.sh的权限问题;同样的,我们赋予权限就可以了:

chmod +x ./build.sh

执行完脚本是这个样子的:


image

为什么会报这样一个错误呢?
这里我们就要弄明白动态库到底是一个什么东西:
1、动态库是编译链接的最终产物(是.o文件链接后的产物)。
2、之前我们讲过静态库.o文件的合集,那么静态库就能够链接成动态库
(这里我们先把上面的问题记录一下,接着往下走)


我们上面是直接将.o链接成.dylib,上面我们也说了静态库可以链接成动态库。那么接下来,我们就在上面的"编译TestExample.o ---> libTestExample.dylib" 这个一步改一下,改成下面的指令:

# Xcode ---> 静态库
libtool -static -arch_only x86_64 TestExample.o -o libTestExample.a

echo "编译libTestExample.a ---> libTestExample.dylib"
# -dynamiclib: 动态库
# 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 -framewoek Foundation \
-all_load \
libTestExample.a -o libTestExample.dylib

注意上面的-all_load,这一点我们再9、iOS强化 --- 静态库静态库的最后讲过,这里因为dylib并没有使用.a文件里面的函数,所有如果不单独设置,默认是-noall_load
运行build.sh:

image
执行test
image
我们发现,test依然报错。

那么dyld: Library not loaded这个错误的是怎么产生的呢?
首先我们要明确一点,我们的动态库是通过dyld在运行时动态加载的。
那么我们在编译的时候只是告诉了test符号,但是在运行过程中,dyld动态加载动态库,此时去找符号的真实的地址,发现找不到。

动态库Framework

下面我们通过Framework来讲解一下,来解决一下上面的问题:

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 \
-I./Headers \
-c TestExample.m -o TestExample.o

echo "编译TestExample.o ---> TestExample.dylib"
# -dynamiclib: 动态库
# 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 \
TestExample.o -o TestExample
# 这里我们就不再外部去修改文件的后缀和文件名了,我们直接生成TestExample动态库

执行结果:

image
这样我们的framework就构建起来了,接下来我们再来编译链接我们的test,脚本:
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./Frameworks/TestExample.framework/Headers \
-c test.m -o test.o

echo "链接test.o ---> test"
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

这就要从dyld加载动态库说起了,首先我们来看下面这张图:

image.png
otool -l test | grep 'DYLIB' -A 5
// -A 向下打印
// -B 向上打印
// 5 五行
image

下面我们就来修改一下动态库的路径。
先介绍一个搜索指令:

otool -l test | grep 'rpath' -A 5 -i
/// 这条指令是大小写敏感的,如果想要大小写不敏感,就在末尾加一个 "-i"
方法一:install_name_tool

通过 install_name_toolid指令,从外部修改LC_ID_DYLIB

image
接下来我们再来看一下test里面的LC_LOAD_DYLIB:
image
此时再运行test就不会报错了。
image
方法二:在生成动态库的时候,就将地址写进入

大家看到上面的方法是在生成动态库之后,才去修改动态库地址。
其实我们可以在生成的过程中,就去修改。
install_name是连接器(ld)的一个参数,我们来看一下:

image
install_name就是用来设置LC_ID_DYLIB的值的。
-install_name @rpath/TestExample.framework/TestExample \
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 \
-install_name @rpath/TestExample.framework/TestExample \
TestExample.o -o TestExample

2、接着在build.sh,最后生成test可执行文件的时候,加上这样一条指令:

-Xlinker -rpath -Xlinker @executable_path/Frameworks \
echo "链接test.o ---> test"
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 \
-Xlinker -rpath -Xlinker @executable_path/Frameworks \
test.o -o test

同样的执行脚本之后,test还是可以运行成功的。

多个动态库嵌套

-Xlinker -rpath -Xlinker @loader_path/Frameworks \

同时,中间动态库处理引入自己的头文件之外,还要引入下一级动态库的头文件

-I./Headers \
-I./Frameworks/TestExampleLog.framework/Headers \

下面讲一下多个动态库的另一个问题:
比如:

image.png
动态库TestExample里面嵌套者SubTestExample,如果说test想要使用SubTestExample里面的函数,这个时候应该怎么办?
因为TestExample里面的符号对于test是暴露的;SubTestExample里面的符号对于TestExample是暴露的;
但是,SubTestExample里面的符号对于test不是暴露。(有兴趣的同学可以打印一下TestExample的导出符号表objdump --macho --exports-trie TestExample

这个时候我们就要用到链接器的参数-reexport_framework

-reexport_framework name[,suffix]
                 This is the same as the -framework name[,suffix] but also specifies that the all symbols
                 in that framework should be available to clients linking to the library being created.
                 This was previously done with a separate -sub_umbrella option.

我们在中间动态库的插件TestExampleBuild.sh里面添加这样一条指令(链接生成动态库的时候,不是编译的时候):

-Xlinker -reexport_framework -Xlinker SubTestExample \

这样,中间动态库就会增加一条Load Command : LC_REEXPORT_DYLIB
这样我们的可执行文件test就可以通过读取LC_REEXPORT_DYLIB找到后面的动态库。
使用的时候,在testbuild.sh里面,test.m -> test.o的时候,引入SubTestExample的头文件:

-I./Frameworks/TestExample.framework/Frameworks/SubTestExample.framework/Headers \

这样test就可以正常使用SubTestExample里面的函数了。

上一篇 下一篇

猜你喜欢

热点阅读