• 【ubuntu】linux链接库


    C标准库和C++的STL是共享元件的例子,可以被我们的程序所链接。这样的好处是:每个对象文件在链接时不需要被陈述,因为开发者可以批量引用库。这简化了应用之间的元素共享和重复利用。

    库类型

    1. 静态库(.a)
    2. 动态库(.so):这种类型的库只有一种形式,但是有两种使用方式:
      1. 在运行时动态链接: 这种库必须在编译/链接阶段可用。 这些共享的对象并不被包含到可执行元件中去,但被绑定到可执行程序。
      2. 动态加载/卸载,在执行时链接(例如:浏览器插件)。

    库命名传统: 通常带有lib前缀,所有C标准库都是满足这样的规律。当链接时,命令行引用这个库时将不包含前缀或者后缀;例如,gcc src-file.c -lm -lpthread这个编译、链接命令,在链接过程中引用的库是数学库m和线程库pthread,这两个库可以在/usr/lib/libm.a/usr/lib/libpthread.a找到。注意:GNU编译器现在有命令行-pthread选项,而老版本的编译器需要通过-lpthread显式指定线程库。因此,你更有可能看到gcc src-file.c -lm -pthread

    静态库(.a)

    命令:cc -Wall -c ctest1.c ctest2.c

    • 选项-Wall:包含警告,可以看到警告的帮助页
    • 创建一个libctest.a库:ar -cvq libctest.a ctest1.o ctest2.o
    • 列出库所包含的文件:ar -t libctest.a
    • 链接这个库:
      • cc -o exe prog.c libctest.a
      • cc -o exe prog.c -L/path/to/library-dir -lctest

    注意:当创建库以后,通过命令行链接并生成可执行程序以后,会在可执行程序的archive中创建一个符号表,可执行程序就嵌入了ar命令,对于MS/Windows开发者来说,.a库和Visual C++的.lib库一样。

    动态链接的共享对象库(.so)

    生成一个共享库方法:(动态链接对象库文件)

    1. 创建一个对象代码
    2. 创建库
    3. 可选:使用符号链接创建默认版本

    创建库实例

    gcc -Wall -fPIC -c *.c
    gcc -shared -W1,-soname,libctest.so.1 -o libctest.so.1.0 *.o
    mv libctest.so.1.o /opt/lib
    ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so.1
    ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so
    

    这会创建一个libctest.so.1.o和一个指向它的符号链接。将链接级联也是合法的:

    ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so.1
    ln -sf /opt/lib/libctest.so.1 /opt/lib/libctest.so
    

    如果查看/lib和/usr/lib下的所有库,以上两种方法都存在,linux开发者并没有统一。重要的是符号链接最终指向的真实库文件。
    -Wall: 包含警告;
    -fPIC:编译器输出独立代码位置,共享库需要的一个特点;
    -shared:产生一个共享库对象,可以被其他对象链接形成一个可执行程序;
    -Wl:可选,传递选项给链接器;在上面的例子里,“-soname libctest.so.1被传递; -o:运算输出,共享对象的名字为libctest.so.1.0`

    库的链接:

    • 链接/opt/lib/libctest.so 允许编译-lctest执行;
    • 链接/opt/lib/libctest.so.a`允许运行时绑定运行;

    编译main程序并链接共享对象库

    gcc -Wall -I/path/to/include-flies -L/path/to/libraries prog.c -lctest -o prog
    gcc -Wall -L/opt/lib prog.c -lctest -o prog
    共享库的名字是libctest.so,这就是为什么你需要创建符号链接,否则你会得到/usr/bin/ld:cannot find -lctest 错误。这些库不会被包含到可执行程序中,在运行过程中动态链接。

    依赖列表

    可执行程序依赖的共享库可以通过`ldd name-of-executable列举出来,当库被加载时,共享连接库中未解析错误可能会导致程序运行错误。
    例如,

      [prompt]$ ldd libname-of-lib.so
            libglut.so.3 => /usr/lib64/libglut.so.3 (0x00007fb582b74000)
            libGL.so.1 => /usr/lib64/libGL.so.1 (0x00007fb582857000)
            libX11.so.6 => /usr/lib64/libX11.so.6 (0x00007fb582518000)
            libIL.so.1 (0x00007fa0f2c0f000)
            libcudart.so.4 => not found
    

    前三个库表明存在路径,而后两个存在问题。解决后两个库的依赖:

    • 方法一:添加为解析库的路径到/etc/ld.so.conf.d/name-of-lib-x86_64.conf 和/或 /etc/ld.so.conf.d/name-of-lib-i686.conf,然后用sodu ldconfig再加载库缓存
    • 方法二:添加库库和路径到编译/链接命令: -lname-of-lib -L/path/to/lib
    • 方法三:添加库路径到环境变量解决运行依赖问题:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/lib

    运行程序

    • 设置路径: export LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH
    • 运行:prog

    主要名词:

    • gcc -GNU的c编译器
    • ld -GNU的链接器
    • ldd -列出库的依赖项
    • ldconfig -配置动态链接器运行时绑定对象(即,更新缓存/etc/ld.so.cache)

    库路径

    为了让可执行程序找到需要的库,并在运行时链接,必须对系统进行一些配置,相关的配置方法有以下:

    1. 添加在动态链接时需要包含的库目录到/etc/ld.so.conf文件中,执行ldconfig(root权限)配置链接器在运行时的绑定对象。你可以使用-f file-name标识引用其他配置文件。
    2. 添加指定目录到库缓存: ldconfig -n /opt/lib。其中,/opt/lib是包含你的库ctest.so的目录,但这种方式不会对系统永久生效,重启后会丢失。例如,当前目录ldconfig -n .,链接使用-L.即可。
    3. 指定环境变量LD_LIBRARY_PATH,使其包含共享库所在的目录。export LD_LIBRARY_PATH=/OPT/LIB:$LD_LIBRARY_PATH,或者编辑~/.bashrc文件:
    ...
    if [ -d /opt/lib ];
    then
       LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH
    fi
    ...
    export LD_LIBRARY_PATH
    

    库信息

    ar:列举在archive库中的所有对象文件
    ar tf /usr/lib/x86_64-linux-gnu/libjpeg.a

    nm:列举符号,包括对象文件、archive库和共享库
    nm file.o #列举对象文件中包含的符号
    nm /usr/lib/x86_64-linux-gnu/libjpeg.a #列举包含在archive库中的符号。
    使用-D列巨额包含在对象文件或共享库中的符号。

    使用libdl动态加载和卸载共享库

    在执行时可以动态加载/卸载这些库。
    库的包含文件ctest.h如下所示:使用extern "c"方便该库可以用在c和c++里。这个语句可以防止链接时因为C++的名字隔离而导致的为解析符号。

    #ifndef CTEST_H
    #define CTEST_H
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    void ctest1(int *);
    void ctest2(int *);
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif
    

    动态加载或卸载libctest.so库(progdl.c):

    #include <stdio.h>
    #include <dlfcn.h>
    #include "ctest.h"
    
    int main(int argc, char **argv) 
    {
       void *lib_handle;
       double (*fn)(int *);
       int x;
       char *error;
    
       lib_handle = dlopen("/opt/lib/libctest.so", RTLD_LAZY);
       if (!lib_handle) 
       {
          fprintf(stderr, "%s
    ", dlerror());
          exit(1);
       }
    
       fn = dlsym(lib_handle, "ctest1");
       if ((error = dlerror()) != NULL)  
       {
          fprintf(stderr, "%s
    ", error);
          exit(1);
       }
    
       (*fn)(&x);
       printf("Valx=%d
    ",x);
    
       dlclose(lib_handle);
       return 0;
    }
    

    编译命令:gcc -rdynamic -o progdl progdl.c -ldl

    解释:

    • dlopen("/opt/lib/libctest.so", RTLD_LAZY);打开名字为”libctest.so“的共享连接库,第二个参数显示绑定,在dlfcn.h文件中定义,如果失败了返回NULL。
      选项: RTLD_LAZY:如果指定,linux并不关心未解析符号直到被引用;RTLD_NOW:所有未解析符号都会被解析在调用dlopen时;RT_GLOBAL,让符号库可见。
    • dlsym(lib_handle, "ctest1"); 换回加载的共享库的函数地址;如果失败返回值为NULL。注意:当使用c++函数时,首先使用nm找到mangles符号名字,或者使用extern "C"避免名字mangling. 例如,extern "C" void function-name();

    C++类对象和动态加载

    C++和名字修饰(name mangling)

    当使用c++编译上面的c例子,会发现c++函数名字被修饰而导致不可用,除非函数定义有extern "C"{}保护。
    注意,以下两者不等价:

    //1:
    extern "C" {
        int functionx();
    }
    //2:
    extern "C" int functionx();
    

    以下两者是等价的:

    //1:
    extern "C" {
        extern int functionx();
    }
    //2:
    extern "C" int functionx();
    

    动态加载C++类:
    动态库加载程序可以使得编程者加载C函数。在C++中,我们想要加载类的成员函数,实际上,整个类可以在库中,我们先要加载并访问整个对象以及它的成员函数。通过传递”C“类工厂函数可以实现。
    类的头文件如下所示:

    class Abc {
    
    ...
    ...
    
    };
    
    // Class factory "C" functions
    
    typedef Abc* create_t;
    typedef void destroy_t(Abc*);
    

    对应的cpp文件如下:

    Abc::Abc()
    {
        ...
    }
    
    extern "C"
    {
       // These two "C" functions manage the creation and destruction of the class Abc
    
       Abc* create()
       {
          return new Abc;
       }
    
       void destroy(Abc* p)
       {
          delete p;   // Can use a base class or derived class pointer here
       }
    }
    

    这个cpp文件是库的源文件,C函数在库中动态加载库中实例化、销毁一个类,Abc就是这个类。

    包含Main可执行程序调用这个库的方法如下:

    // load the symbols
        create_t* create_abc = (create_t*) dlsym(lib_handle, "create");
    
    ...
    ...
    
        destroy_t* destroy_abc = (destroy_t*) dlsym(lib_handle, "destroy");
    
    ...
    ...
    

    注意: C++类的new/delete应该都由可执行程序或者库来提供,不应该分开。以便如果在一方出现了new/delete的重载不会导致意外情况。

    和DLL的比较

    windows下和linux/Unix共享对象(.so)对应的是.dll,通常windows下是.dll,有时也可能是.ocx。 在旧的16位系统上,动态链接库也可能为.exe。不幸的是,windows下Dll的生成和Microsoft IDE紧密结合,没有IDE基本没有办法自己生成。
    windows c++对应的函数:

    • ::LoadLibrary() - dlopen()
    • ::GetProcAddress() -dlsym()
    • ::FreeLibrary() -dlclose()

    跨平台代码片段

    包含头文件(.h/.hpp):

    class Abc{
    public:
       static Abc* Instance(); // Function declaration. Could also be used as a public class member function.
    
    private:
       static Abc *mInstance;      // Singleton. Use this declaration in C++ class member variable declaration.
       ...
    }
    

    对应的cpp文件:

    /// Singleton instantiation
    Abc* Abc::mInstance = 0;   // Use this declaration for C++ class member variable
                               // (Defined outside of class definition in ".cpp" file)
    
    // Return unique pointer to instance of Abc or create it if it does not exist.
    // (Unique to both exe and dll)
    
    static Abc* Abc::Instance() // Singleton
    {
    #ifdef WIN32
        // If pointer to instance of Abc exists (true) then return instance pointer else look for 
        // instance pointer in memory mapped pointer. If the instance pointer does not exist in
        // memory mapped pointer, return a newly created pointer to an instance of Abc.
    
        return mInstance ? 
           mInstance : (mInstance = (Abc*) MemoryMappedPointers::getPointer("Abc")) ? 
           mInstance : (mInstance = (Abc*) MemoryMappedPointers::createEntry("Abc",(void*)new Abc));
    #else
        // If pointer to instance of Abc exists (true) then return instance pointer 
        // else return a newly created pointer to an instance of Abc.
    
        return mInstance ? mInstance : (mInstance = new Abc);
    #endif
    }
    

    windows链接器将会链接入两个对象实例,一个在exe,一个在可加载模块内。通过内存映射指针可对两者进行明确,以便exe和可加载库指向同一个变量或者对象。而GNU 链接器不存在这个问题。
    交叉平台可加载库的编程:

    #ifndef USE_PRECOMPILED_HEADERS
    #ifdef WIN32
    #include <direct.h>
    #include <windows.h>
    #else
    #include <sys/types.h>
    #include <dlfcn.h>
    #endif
    #include <iostream>
    #endif
    
        using namespace std;
    
    #ifdef WIN32
        HINSTANCE lib_handle;
    #else
        void *lib_handle;
    #endif
    
        // Where retType is the pointer to a return type of the function
        // This return type can be int, float, double, etc or a struct or class.
    
        typedef retType* func_t;  
    
        // load the library -------------------------------------------------
    #ifdef WIN32
        string nameOfLibToLoad("C:optliblibctest.dll");
        lib_handle = LoadLibrary(TEXT(nameOfLibToLoad.c_str()));
        if (!lib_handle) {
            cerr << "Cannot load library: " << TEXT(nameOfDllToLoad.c_str()) << endl;
        }
    #else
        string nameOfLibToLoad("/opt/lib/libctest.so");
        lib_handle = dlopen(nameOfLibToLoad.c_str(), RTLD_LAZY);
        if (!lib_handle) {
            cerr << "Cannot load library: " << dlerror() << endl;
        }
    #endif
    
    ...
    ...
    ...
    
        // load the symbols -------------------------------------------------
    #ifdef WIN32
        func_t* fn_handle = (func_t*) GetProcAddress(lib_handle, "superfunctionx");
        if (!fn_handle) {
            cerr << "Cannot load symbol superfunctionx: " << GetLastError() << endl;
        }
    #else
        // reset errors
        dlerror();
    
        // load the symbols (handle to function "superfunctionx")
        func_t* fn_handle= (func_t*) dlsym(lib_handle, "superfunctionx");
        const char* dlsym_error = dlerror();
        if (dlsym_error) {
            cerr << "Cannot load symbol superfunctionx: " << dlsym_error << endl;
        }
    #endif
    
    ...
    ...
    ...
    
        // unload the library -----------------------------------------------
    
    #ifdef WIN32
        FreeLibrary(lib_handle);
    #else
        dlclose(lib_handle);
    #endif
    
  • 相关阅读:
    CSS选择符-----关系选择符
    CSS选择符-----元素选择符
    jQuery效果--show([speed,[easing],[fn]])和hide([speed,[easing],[fn]])
    大型网站架构系列:电商网站架构案例
    大型网站架构系列:负载均衡详解(上)
    JBOSS集群和安装
    webwork或Struts配置网站根路径的默认页面办法
    SQL Server 删除重复记录,只保留一条记录
    删除JBOSS eap4.3下的jmx-console、web-console、ws-console、status服务
    SLF4J versions 1.4.0 and later requires log4j 1.2.12 or later 终极解决
  • 原文地址:https://www.cnblogs.com/imagezy/p/8320824.html
Copyright © 2020-2023  润新知