• OS X 下动态库的引用


    foo.c

    #include <stdio.h>
    
    void foo(void)
    {
        printf("foo.
    ");
    }

    main.c

    #include <stdio.h>
    
    extern void foo(void);
    
    int main(void)
    {
        foo();
        printf("main.
    ");
        return 0;
    }

    编译执行

    
    $ gcc -dynamiclib foo.c -o libfoo.dylib
    (-dynamiclib 表示将foo.c编译成一个动态库,
    -o libfoo.dylib 用于指定生成的动态库的名称)
    
    $ gcc main.c -L. -lfoo -o main
    (-L. 指定当前目录为链接时动态库的查找目录,
    -lfoo 指定要链接的动态库为libfoo.dylib,
    -o main 指定生成的可执行文件名称为main)
    
    $ ./main
    foo.
    main.
    (在当前目录下执行main,注意要加上 ./)
    

    两种引用

    
    动态库的引用分为链接时引用与运行时引用两种情况。
    
    -L. -lfoo 表示在链接时引用当前目录下的libfoo.dylib,
    如果我们把libfoo.dylib移到上一层目录:
    $ mv libfoo.dylib ..
    这时再次链接:
    $ gcc main.c -L. -lfoo -o main
    ld: library not found for -lfoo
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    会提示出错,再将动态库移回:
    $ mv ../libfoo.dylib .
    再次链接:
    $ gcc main.c -L. -lfoo -o main
    执行成功。
    
    
    在执行上述链接过程时,编译器顺便将动态库libfoo.dylib的install_name记录到了main程序中,
    用来在运行时查找并引用该动态库,
    查看动态库libfoo.dylib的install_name,运行:
    $ otool -D libfoo.dylib
    libfoo.dylib:
    libfoo.dylib
    显示出libfoo.dylib的install_name是libfoo.dylib,
    查看一下main程序中记录的install_name是否与此一致:
    $ otool -L main
    main:
        libfoo.dylib (compatibility version 0.0.0, current version 0.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
    第一条就是动态库libfoo.dylib对应的install_name,显然两者是一致的,main程序在运行时会根据记录的这些install_name在文件系统中查找所依赖的动态库。
    

    关于install_name

    
    (注意:只有动态库才有install_name,应用程序如果引用了某一个动态库,则会在链接时记录该动态库的install_name。)
    
    在链接时,是可以为动态库指定一个install_name的:
    $ gcc -dynamiclib foo.c -install_name aaa -o libfoo.dylib
    将该动态库的install_name指定为aaa了,如不放心,可以查看一下:
    $ otool -D libfoo.dylib 
    libfoo.dylib:
    aaa
    确实为aaa。(注意:如不指定,则会有一个缺省值,如libfoo.dylib)
    
    重新编译main程序:
    $ gcc main.c -L. -lfoo -o main
    查看一下main程序中记录的install_name:
    $ otool -L main
    main:
        aaa (compatibility version 0.0.0, current version 0.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
    果然是aaa。这时再次运行main程序:
    $ ./main
    dyld: Library not loaded: aaa
      Referenced from: /private/tmp/dylib/./main
      Reason: image not found
    Trace/BPT trap: 5
    出错了,提示找不到动态库aaa,这是因为main程序把aaa当成一个路径,而当前路径下确实没有叫aaa的动态库,
    我们可以把libfoo.dylib改名为aaa:
    $ mv libfoo.dylib aaa
    再次运行:
    $ ./main 
    foo.
    main.
    正常。
    

    install_name_tool

    
    顾名思义,install_name_tool就是用来操作install_name的工具,
    它既可以改变动态库自身的install_name,也可以改变应用程序中记录的所引用动态库的install_name。
    
    首先将动态库名称由aaa改为bbb:
    $ mv aaa bbb
    查看一下动态库的install_name:
    $ otool -D bbb 
    bbb:
    aaa
    还是之前指定的aaa。
    现在我们利用install_name_tool把动态库的install_name也改为bbb:
    $ install_name_tool -id bbb bbb
    (前一个bbb表示我们指定的install_name,后一个bbb表示动态库文件名)
    查看一下修改之后的结果:
    $ otool -D bbb 
    bbb:
    bbb
    果然改成了bbb。
    
    这时再查看一下main程序中记录的install_name:
    $ otool -L main
    main:
        aaa (compatibility version 0.0.0, current version 0.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
    还是之前的aaa,显然如果这时运行main程序还是会失败,因为当前路径下没有叫aaa的动态库文件,
    下面我们就将main程序中记录的aaa改为bbb:
    $ install_name_tool -change aaa bbb main
    (aaa是旧的名称,bbb是新的名称,main表示要操作的文件名称)
    查看一下结果:
    $ otool -L main
    main:
        bbb (compatibility version 0.0.0, current version 0.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
    修改成功,这时再运行一下main程序:
    $ ./main 
    foo.
    main.
    运行成功。
    
    现在动态库bbb是和main程序在同一个目录,如果把bbb移到/tmp目录:
    $ mv bbb /tmp
    我们只需要将main程序中记录的bbb改为/tmp/bbb:
    $ install_name_tool -change bbb /tmp/bbb main
    查看一下结果:
    $ otool -L main
    main:
        /tmp/bbb (compatibility version 0.0.0, current version 0.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
    这样也是可以运行的:
    $ ./main 
    foo.
    main.
    
    相对路径也是可以的,现在把tmp目录下的bbb移到当前路径下的temp目录:
    $ mkdir temp
    $ mv /tmp/bbb temp/
    我们只需要将main程序中记录的/tmp/bbb改为./temp/bbb:
    $ install_name_tool -change /tmp/bbb ./temp/bbb main
    查看一下结果:
    $ otool -L main
    main:
        ./temp/bbb (compatibility version 0.0.0, current version 0.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
    这样也是可以运行的:
    $ ./main 
    foo.
    main.
    

    @executable_path/ @loader_path/ @rpath/

    
    这三个path是用来表示路径的变量,它们可以用于install_name中,如:
    @executable_path/../Frameworks/libAAA.dylib
    @loader_path/libBBB.dylib
    @rpath/libCCC.dylib
    (注意:在使用这三个变量时,后面的“/”不能少。)
    

    @executable_path/

    
    .
    ├── Frameworks
    │   ├── bar.c
    │   └── foo.c
    └── MacOS
        └── main.c
    
    MacOS/main.c 
    
    #include <stdio.h>
    
    extern void foo(void);
    extern void bar(void);
    
    int main(void)
    {
        foo();
        bar();
        printf("main.
    ");
        return 0;
    }
    
    Frameworks/foo.c 
    
    #include <stdio.h>
    
    void foo(void)
    {
        printf("foo.
    ");
    }
    
    Frameworks/bar.c
    
    #include <stdio.h>
    
    void bar(void)
    {
        printf("bar.
    ");
    }
    
    先把foo.c和bar.c做成动态库:
    $ gcc foo.c -dynamiclib -install_name @executable_path/../Frameworks/libfoo.dylib -o libfoo.dylib
    $ gcc bar.c -dynamiclib -install_name @executable_path/../Frameworks/libbar.dylib -o libbar.dylib
    
    然后利用两个动态库编译main.c:
    $ gcc main.c -L../Frameworks -lfoo -lbar -o main
    
    查看一下main程序引用的动态库:
    $ otool -L main
    main:
        @executable_path/../Frameworks/libfoo.dylib (compatibility version 0.0.0, current version 0.0.0)
        @executable_path/../Frameworks/libbar.dylib (compatibility version 0.0.0, current version 0.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
    @executable_path/在运行时会替换成当前程序(也就是main程序)所在路径,
    我们之前做的动态库文件libfoo.dylib、libbar.dylib正好存在于Frameworks目录下,所以该程序可以运行:
    $ ./main 
    foo.
    bar.
    main.
    

    @loader_path/

    
    .
    ├── A
    │   ├── B
    │   │   └── bar.c
    │   └── foo.c
    └── main.c
    
    main.c
    
    #include <stdio.h>
    
    extern void foo(void);
    
    int main(void)
    {
        foo();
        printf("main.
    ");
        return 0;
    }
    
    A/foo.c
    
    #include <stdio.h>
    
    extern void bar(void);
    
    void foo(void)
    {
        bar();
        printf("foo.
    ");
    }
    
    A/B/bar.c
    
    #include <stdio.h>
    
    void bar(void)
    {
        printf("bar.
    ");
    }
    
    先将bar.c编译成动态库:
    $ gcc bar.c -dynamiclib -install_name @loader_path/B/libbar.dylib -o libbar.dylib
    
    再将foo.c编译成动态库(用到了libbar.dylib):
    $ gcc foo.c -dynamiclib -LB -lbar -install_name @executable_path/A/libfoo.dylib -o libfoo.dylib
    
    最后再编译main程序(用到了libfoo.dylib):
    $ gcc main.c -LA -lfoo -o main
    
    执行:
    $ ./main
    bar.
    foo.
    main.
    
    main程序依赖于libfoo.dylib,@executable_path/就是main程序的执行路径,
    所以libfoo.dylib的install_name设置为@executable_path/A/libfoo.dylib正合适。
    
    libfoo.dylib依赖于libbar.dylib,@loader_path/就是libfoo.dylib的加载路径,
    所以libbar.dylib的install_name设置为@loader_path/B/libbar.dylib正合适。
    (如果用@executable_path/,则可设置为@executable_path/A/B/libbar.dylib)
    

    @rpath/

    
    一个应用程序只有一个@executable_path/,可能有多个@loader_path/。
    @executable_path/表示应用程序执行时路径,@loader_path/表示动态库(包括应用程序)加载时路径,
    程序执行时,这两个路径由系统赋值。然而@rpath/则是由用户随意赋值,比前者更为灵活。
    
    .
    ├── A
    ├── B
    ├── C
    ├── foo.c
    └── main.c
    
    main.c
    
    #include <stdio.h>
    
    extern void foo(void);
    
    int main(void)
    {
        foo();
        printf("main.
    ");
        return 0;
    }
    
    foo.c
    
    #include <stdio.h>
    
    void foo(void)
    {
        printf("foo.
    ");
    }
    
    首先编译动态库,把@rpath/加入到install_name中:
    $ gcc foo.c -dynamiclib -install_name @rpath/libfoo.dylib -o libfoo.dylib
    
    然后编译应用程序,并添加自定义rpath
    $ gcc main.c -L. -lfoo -Wl,-rpath,@loader_path/A/ -Wl,-rpath,@loader_path/B/ -Wl,-rpath,@loader_path/C/ -o main
    
    我们可以用命令查看一下这三个rpath:
    $ otool -l main | grep path
             name @rpath/libfoo.dylib (offset 24)
             path @loader_path/A/ (offset 12)
             path @loader_path/B/ (offset 12)
             path @loader_path/C/ (offset 12)
    第一行name表示main程序记录的动态库的install_name,后面三个path组成了rpath列表,
    运行时@rpath/就被依次替换成了@loader_path/A/ 、@loader_path/B/ 、 @loader_path/C/,
    @loader_path/又最终被替换成了动态库(或应用程序)的加载路径。
    
    将libfoo.dylib分别移动到A、B、C三个目录,测试main程序是否可以正常运行:
    $ mv libfoo.dylib A/
    $ ./main 
    foo.
    main.
    $ mv A/libfoo.dylib B/
    $ ./main 
    foo.
    main.
    $ mv B/libfoo.dylib C/
    $ ./main 
    foo.
    main.
    

    Edit By MaHua

  • 相关阅读:
    Ajax技术应用方面
    关于tomcat环境配置的疑惑(tomcat未进行任何环境配置仍成功显示welcome页面)
    jsp中动态include与静态include的区别
    简单说说tomcat7.0的配置
    传统开发模式与Ajax开发模式的区别
    认识Ajax
    tomcat与jdk的关系
    org.hibernate.TransactionException: nested transactions not supported
    解读Tomcat7.0的startup.bat批处理命令
    forward和redirect的区别
  • 原文地址:https://www.cnblogs.com/liubaocheng999/p/4285256.html
Copyright © 2020-2023  润新知