动态库

前言:

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

image.png

//test.m内容如下
#import 
#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 
#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 
#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 
#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

你可能感兴趣的:(动态库)