动态库

前言:

我们还使用静态库中的案例

image.png

//test.m内容如下
#import <Foundation/Foundation.h>
#import "TestExample.h"

int main(){
    NSLog(@"testApp----");
//    TestExample *manager = [TestExample new];
//    [manager lg_test: nil];
   return 0;
}

执行build.sh脚本

$ ./build.sh
$ objdump --macho -d test

TestExample并没有被链接到test中,因为dead code strip默认选择-noall_load,此时需配置 -Xlinker -all_load
接下来对test.m内容进行修改如下

#import <Foundation/Foundation.h>
#import "TestExample.h"
// 全局符号
void global_function() {
    
}
int main(){
//    global_function();
    NSLog(@"testApp----");
//    TestExample *manager = [TestExample new];
//    [manager lg_test: nil];
    return 0;
}

定义的全局符号global_function并没有使用,执行完脚本,查看test的符号表发现全局符号global_function依然会导出,此时需配置 #-Xlinker -dead_strip \ 把未使用的全局符号剥离,然而TestExample虽然未被使用,但是却没有被剥离掉,是因为OC是一门动态语言。
接下来我们再次对test.m内容进行修改如下

#import <Foundation/Foundation.h>
#import "TestExample.h"
// 全局符号
void global_function() {
    
}
int main(){
    global_function();
    NSLog(@"testApp----");
//    TestExample *manager = [TestExample new];
//    [manager lg_test: nil];
    return 0;
}
// 执行脚本并且查看符号表
$ ./build.sh
$ objdump --macho --syms test

这个时候全局符号global_function会被导出,我们可以配置-Xlinker -why_live -Xlinker _global_function \查看global_function为什么会被导出?

//最终脚本内容如下
SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk
FILE_NAME=test
HEADER_SEARCH_PATH=./StaticLibrary

echo "-----开始编译test.m"
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot $SYSROOT \
-I${HEADER_SEARCH_PATH} \
-c ${FILE_NAME}.m -o ${FILE_NAME}.o

echo "-----开始进入StaticLibrary"
pushd ./StaticLibrary

clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot $SYSROOT \
-c TestExample.m -o TestExample.o

ar -rc libTestExample.a TestExample.o
echo "-----开始退出StaticLibrary"
popd

echo "-----开始test.o to test EXEC"
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot $SYSROOT \
-Xlinker -dead_strip \
-Xlinker -all_load \
-Xlinker -why_live -Xlinker _global_function \
-L./StaticLibrary \
-lTestExample \
${FILE_NAME}.o -o ${FILE_NAME}
// 执行脚本
$ ./build.sh
-----开始test.o to test EXEC
_global_function from test.o
  _main from test.o
    _main from test.o

可以看出来全局符号被test.o中的_main使用

一. 动态库原理

1.1准备文件如下,内容与静态库一样

image.png
我们使用创建的build.sh,来使用脚本

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

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 "编译TestExample.m --- libTestExample.dylib"
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

运行后报错dyld: Library not loaded: libTestExample.dylib

image.png
1.2.先生成静态库再链接成动态库

//把o文件变成a文件,x86_64是指定的架构。也可以使用ar -rc a.a a.o
libtool -static -arch_only x86_64 TestExample.o -o libTestExample.a
//把.a文件变成.dylib文件
ld -dylib -arch x86_64 \
-macosx_version_min 11.0 \
-syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
-lsystem -framework Foundation \
-all_load \  //如果没有这个参数,链接器会默认not all load
libTestExample.a -o libTestExample.dylib

运行之后还是报错dyld: Library not loaded: libTestExample.dylib
image.png

查看test链接的动态库

$ otool -l test | grep 'DYLIB'
          cmd LC_LOAD_DYLIB
          cmd LC_LOAD_DYLIB
          cmd LC_LOAD_DYLIB
          cmd LC_LOAD_DYLIB
          cmd LC_LOAD_DYLIB

接下来查看链接的动态库信息,让向下多显示5行动态库信息 注意!!! -A是向下 -B是向上

$ otool -l test | grep 'DYLIB' -A 5
          cmd LC_LOAD_DYLIB
      cmdsize 48
         name libTestExample.dylib (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 0.0.0
compatibility version 0.0.0
--
          cmd LC_LOAD_DYLIB
      cmdsize 96
         name /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 1770.255.0
compatibility version 300.0.0
--
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name /usr/lib/libobjc.A.dylib (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 228.0.0
compatibility version 1.0.0
--
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name /usr/lib/libSystem.B.dylib (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 1292.60.1
compatibility version 1.0.0
--
          cmd LC_LOAD_DYLIB
      cmdsize 104
         name /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 1770.255.0
compatibility version 150.0.0

可以看到我们自己创建的库 name libTestExample.dylib (offset 24),而其他的动态库的name却是库的路径。
我们的动态库能够自己保存自己的路径

$ otool -l ./dylib/libTestExample.dylib | grep 'ID' -A 5
          cmd LC_ID_DYLIB
      cmdsize 48
         name libTestExample.dylib (offset 24)
   time stamp 1 Thu Jan  1 08:00:01 1970
      current version 0.0.0
compatibility version 0.0.0
--
     cmd LC_UUID
 cmdsize 24
    uuid 296915D5-4E95-3C0B-BC55-740E357860F4
Load command 9
      cmd LC_BUILD_VERSION
  cmdsize 32

我们看到name 还是 libTestExample.dylib

我们可以使用install_name_tool去给动态库添加路径,注意⚠️使用install_name_tool添加路径需要进入到libTestExample.dylib所在目录,否则会报下面的错误
image.png
$ install_name_tool -id /Users/wangning/Documents/资料/1:22/动态库/上课代码/动态库原理/dylib/libTestExample.dylib libTestExample.dylib

重新查看动态库的LC_ID_DYLIB

$ otool -l libTestExample.dylib | grep 'ID' -A 5
          cmd LC_ID_DYLIB
      cmdsize 128
         name /Users/wangning/Documents/资料/1:22/动态库/上课代码/动态库原理/dylib/libTestExample.dylib (offset 24)
   time stamp 1613027333 Thu Feb 11 15:08:53 2021
      current version 0.0.0
compatibility version 0.0.0
--
     cmd LC_UUID
 cmdsize 24
    uuid 296915D5-4E95-3C0B-BC55-740E357860F4
Load command 9
      cmd LC_BUILD_VERSION
  cmdsize 32

注意⚠️ 此时重新执行build.sh脚本时,要把pushd~popd之间合并libTestExample.dylib操作屏蔽掉,只保留合并test的操作,不然上面install_name_tool的路径会被覆盖掉
然后重新编译和链接我们的test文件运行成功
1.3更改上面的绝对路径为相对路径
上面指定的路径是绝对路径,如果动态库换了位置,还是会出现Reason: image not found
@rpath解决绝对路径的困扰
@rpath Runpath search Paths dyld搜索路径
运行时@rpath指示dyld按顺序搜索路径列表,以找到动态库。
@rpath保存一个或多个路径的变量
所以我们修改脚本

// TestExample.o -o libTestExample.dylib这一步操作过程中添加
-install_name @rpath/dylib/libTestExample.dylib

修改脚本之后执行,然后给test文件添加rpath路径

$ install_name_tool -add_rpath /Users/wangning/Documents/资料/1:22/动态库/上课代码/动态库与framework test

上面配置完成之后,链接我们的test文件运行成功
小提示
如果上面配置rpath之后,链接运行报错,可以执行下面命令查看路径是否配置正确

$ otool -l  test | grep 'RPATH' -A 5
// 还可以查看动态库路径 -i参数,防止大小写敏感
$ otool -l  test | grep 'dylib' -A 3 -i

1.4 executable_path和loader_path

image.png
可以看出LC_RPATH是绝对路径,所以需要用到下面路径来彻底解决绝对路径的问题
@executable_path:表示可执行程序所在的目录,解析为可执行文件的绝对路径。
@loader_path:表示被加载的Mach-O 所在的目录,每次加载时,都可能 被设置为不同的路径,由上层指定。

// 由于上面添加过rpath,接下来修改rpath路径
$ install_name_tool -rpath /Users/wangning/Documents/资料/1:22/动态库/上课代码/动态库原理 @executable_path test

再次查看
image.png

链接运行test文件成功,完美解决
image.png

二. 动态库链接动态库

test.m文件使用Frameworks中的TestExample.framework
在TestExample.framework库中使用TestExampleLog.framework
2.1 先编译并链接TestExampleLog.framework

// install_name是提供给外部需要链接TestExampleLog的路径。
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 TestExampleLog.m -o TestExampleLog.o

clang -dynamiclib  \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-Xlinker -install_name -Xlinker @rpath/TestExampleLog.framework/TestExampleLog \
TestExampleLog.o -o TestExampleLog

image.png
2.2 再编译链接TestExample.framework

// install_name是提供给调用者加载的路径
// rpath是提供给调用TestExampleLog的初始路径
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 \
-I./Frameworks/TestExampleLog.framework/Headers \
-c TestExample.m -o TestExample.o

clang -dynamiclib  \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample \
-Xlinker -rpath -Xlinker @loader_path/Frameworks \
-F./Frameworks \
-framework TestExampleLog \
TestExample.o -o TestExample

2.3编译并链接test

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

clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-Xlinker -rpath -Xlinker @executable_path/Frameworks \
-F./Frameworks \
-framework TestExample \
test.o -o test

链接运行test文件成功

image.png
24 test文件怎么使用TestExampleLog
如果我们想使用TestExampleLog,我们需要能再TestExample中导出符号中能看到TestExampleLog

$ objdump --macho --exports-trie TestExample
TestExample:
Exports trie:
0x000080C0  _OBJC_METACLASS_$_TestExample
0x000080E8  _OBJC_CLASS_$_TestExample

我们没有看出有导出符号,所以我们无法在test使用TestExampleLog,需要使用reexport_framework
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 \
-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample \
-Xlinker -rpath -Xlinker @loader_path/Frameworks \
-Xlinker -reexport_framework -Xlinker TestExampleLog \
-F./Frameworks \
-framework TestExampleLog \
TestExample.o -o TestExample

查看DYLIIB信息

$  otool -l TestExample | grep 'DYLIB' -A 5
          cmd LC_ID_DYLIB
      cmdsize 72
         name @rpath/TestExample.framework/TestExample (offset 24)
   time stamp 1 Thu Jan  1 08:00:01 1970
      current version 0.0.0
compatibility version 0.0.0
--
          cmd LC_REEXPORT_DYLIB
      cmdsize 72
         name @rpath/TestExampleLog.framework/TestExampleLog (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 0.0.0
compatibility version 0.0.0
--

TestExample重新加一个cmd,LC_REEXPORT_DYLIB
我们在test中导入头文件

#import <Foundation/Foundation.h>
#import "TestExample.h"
#import "TestExampleLog.h"

int main(){
    NSLog(@"testApp----");
    TestExample *manager = [TestExample new];
    [manager lg_test: nil];
    TestExampleLog *log = [TestExampleLog new];
    NSLog(@"testApp----%@",log);
    return 0;
}

成功引用TestExampleLog类
image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,841评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,415评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,904评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,051评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,055评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,255评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,729评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,377评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,517评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,420评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,467评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,144评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,735评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,812评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,029评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,528评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,126评论 2 341

推荐阅读更多精彩内容

  • 1.动态库原理 1.1自己生成动态库(失败) 我们还是用在静态库[https://www.jianshu.com/...
    MonKey_Money阅读 1,724评论 0 0
  • 常用库文件格式 .a:静态库 .dylib:动态库 .framework:动静结合的库 .xcframework:...
    Mjs阅读 1,018评论 0 1
  • 上一片文章[https://www.jianshu.com/p/4a0353d4e15d]主要讲述了静态库,这一片...
    qinghan阅读 550评论 0 1
  • 什么是库,使用库有哪些好处?库就是将代码编译成一个二进制文件,再加头文件。常见的库文件格式有.a .dylib ...
    崔希羽阅读 989评论 0 1
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,458评论 16 22