如何通过在App运行时通过dlopen 和dlsym 加载动态库

基础篇-使用

1.通过Xcode-新建工程-Framework-动态库(LibFramework)
  • 准备测试代码 新建Class LibOC

    LibOC.h
    • OC
    1
    2
    3
    4
    5
    int func(int num){
    printf("c-函数调用%d",num);
    return num +1;
    }
    - (int)func:(int)num;
    LibOC.m
    • OC
    1
    2
    3
    4
    - (int)func:(int)num{
    NSLog(@"oc-方法调用%d", num);
    return num+1;
    }
2. build (Any Device Arm64)
  • Products下生成Framework后查看包内容找到可执行文件&& LibOC.h,后续使用
3. 新建动态库测试工程 LibDemo
  • 构建后将第二步骤可执行文件拷贝到.app里面,此时存在两个可执行文件LibDemo、LibFramework
  • 动态库LibOC.h文件直接引入工程, 用与runtime调试
  • Build Phases Run Script 添加脚本文件-代码签名

    sh.bash
    • bash
    1
    2
    /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY"                               
    "$BUILT_PRODUCTS_DIR/$TARGET_NAME.app/LibFramework"
4. 添加动态库调用代码
  • RunTime
main.m
  • OC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void loadRunTime(){
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"LibFramework" ofType:nil];
void *lib_handle = dlopen([bundlePath UTF8String], RTLD_LAZY);
if (!lib_handle) {
printf("%s:loadRunTime没有打开lib:%s",__FILE__,dlerror());
exit(EXIT_FAILURE);
}
Class class_lib = objc_getClass("LibOC");
LibOC *lib = [class_lib new];
int num = [lib func:100];
printf("newNum: %d ---",num);

if (dlclose(lib_handle)!= 0) {
printf("%s:loadRunTime没有关闭lib:%s",__FILE__,dlerror());
exit(EXIT_FAILURE);
}
}
  • dlopen&dlsym
main.m
  • OC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void loadFunc(){
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"LibFramework" ofType:nil];
void *lib_handle = dlopen([bundlePath UTF8String], RTLD_LAZY);
if (!lib_handle) {
NSLog(@"%s:loadFunc没有打开lib:%s",__FILE__,dlerror());
exit(EXIT_FAILURE);
}

func_pointer *func = dlsym(lib_handle, "func");
if(func){
int num = func(100);
printf("newNum: %d ---",num);
}
if (dlclose(lib_handle)!= 0) {
printf("%s:loadFunc没有关闭lib:%s",__FILE__,dlerror());
exit(EXIT_FAILURE);
}
}
4. 调试
  • 增加调用查看输出是否正确

分析

静态库
  • 程序编译时链接到Mach-o 每次更新需要重新编译,使用LLD 链接
  • 我们自购件的各类文件等都为静态库方式
动态库
  • 动态库是运行时加载的库。编译阶段标记为符号未定义,使用DYLD 动态链接
  • 运行时通过dlopen找到库的路径,通多dlsym获取函数的地址调用
  • 动态库使用共享缓存来优化启动速度
  • 动态库的加载分为两种方式

程序启动时绑定

程序第一次使用时绑定

使用MachOView查看可执行文件和动态符号Type
第一步 方式一 验证运行时符号绑定
  • 查看LidDermo–> Symbol table dlepen & dlsym

    符号为:U_UNDF 
    
    加载方式为 NON_LAZY
    
第二步 方式一 验证运行时符号绑定
  • 创建 Hello(.h&m) ()

    Hello.m
    • h
    • m
    1
    2
    3
    4
    #import <Foundation/Foundation.h>
    @interface Hello : NSObject
    - (void)say;
    @end
{% tabbed_codeblock HelloWork.m %}
      
          #import "Hello.h"
          int main(int argc, char *argv[]){
          @autoreleasepool {
          Hello *boy = [[Hello alloc] init]; [boy say];
          return 0;   
          }
          }
      
  {% endtabbed_codeblock %}    
第三步
  1. 输出可执行文件
    • xcrun clang -c hello.m
    • xcrun clang -c HelloWork.m
    • xcrun clang Hello.o HelloWork.o -Wl,xcrun —show-sdk-path/System/Library/Frameworks/Foundation.framework/Foundation
  • 分别输出 Hello.o、 HelloWork.o、a.out
  1. nm 工具查看符号定义情况
  • Hello.o
(undefined) external _NSLog
(undefined) external _OBJC_CLASS_$_NSObject
(undefined) external _OBJC_METACLASS_$_NSObject
(undefined) external ___CFConstantStringClassReference
(undefined) external __objc_empty_cache
0000000000000000 (__TEXT,__text) non-external -[Hello say]
0000000000000060 (__DATA,__objc_const) non-external __OBJC_METACLASS_RO_$_Hello
00000000000000a8 (__DATA,__objc_const) non-external __OBJC_$_INSTANCE_METHODS_Hello
00000000000000c8 (__DATA,__objc_const) non-external __OBJC_CLASS_RO_$_Hello
0000000000000110 (__DATA,__objc_data) external _OBJC_METACLASS_$_Hello
0000000000000138 (__DATA,__objc_data) external _OBJC_CLASS_$_Hello
  • HelloWork.o
(undefined) external _OBJC_CLASS_$_Hello
(undefined) external _objc_alloc_init
(undefined) external _objc_autoreleasePoolPop
(undefined) external _objc_autoreleasePoolPush
(undefined) external _objc_msgSend
0000000000000000 (__TEXT,__text) external _main
0000000000000060 (__DATA,__objc_classrefs) non-external _OBJC_CLASSLIST_REFERENCES_$_
0000000000000070 (__DATA,__objc_selrefs) non-external _OBJC_SELECTOR_REFERENCES_
  • a.out
  (undefined) external _NSLog (from Foundation)
(undefined) external _OBJC_CLASS_$_NSObject (from libobjc)
(undefined) external _OBJC_METACLASS_$_NSObject (from libobjc)
(undefined) external ___CFConstantStringClassReference (from CoreFoundation)
(undefined) external __objc_empty_cache (from libobjc)
(undefined) external _objc_alloc_init (from libobjc)
(undefined) external _objc_autoreleasePoolPop (from libobjc)
(undefined) external _objc_autoreleasePoolPush (from libobjc)
(undefined) external _objc_msgSend (from libobjc)
(undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100003eb0 (__TEXT,__text) non-external -[Hello say]
0000000100003ee0 (__TEXT,__text) external _main
0000000100008020 (__DATA,__objc_const) non-external __OBJC_METACLASS_RO_$_Hello
0000000100008068 (__DATA,__objc_const) non-external __OBJC_$_INSTANCE_METHODS_Hello
0000000100008088 (__DATA,__objc_const) non-external __OBJC_CLASS_RO_$_Hello
00000001000080e0 (__DATA,__objc_data) external _OBJC_METACLASS_$_Hello
0000000100008108 (__DATA,__objc_data) external _OBJC_CLASS_$_Hello
0000000100008130 (__DATA,__data) non-external __dyld_private
第四步
  • (undefined) external 标识未定义 菲私有
  • _OBJC_CLASS_$_Hello 标识OC -> Hello
  • external _main 标识处理地址00,位置在text段
  • from Foundation 标识未定义符号属于 哪个 lib
第五步
  • xcrun otool -L a.out

     /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1770.255.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1292.60.1)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1770.255.0)
    /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
    

输出了各个系统库的来源。正式我们三步使用的路径

总结
  • dylib 格式代表动态链接,在程序运行时才会链接,编译时不会链接到Mach-o,也不会增加包大小。不用更新程序就可以执行新库
  • 关于共享缓存(/var/db/dyld/),苹果维护了一块内存区域,存储了加载过的动态库。程序运行时,先会查找共享缓存,App虚拟进程地址空间会优先映射共享缓存,提高启动速度
  • dyld 根据 undefined 找到 dylib。通过共享缓存递归lib
  • 绑定符号与地址