Linux中动态库的生成与调用

目录

一、C++动态库的生成

1.1 动态库源码

1.1.1 声明文件

// g++ myso.cpp -fPIC -shared -o myso.so 

#include <cstdint>
#include <iostream>

typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;

typedef int8_t  int8;
typedef int16_t int16;
typedef int32_t int32;

extern "C" uint8 GetData(uint8* data, uint16 len);

extern "C" void RecvDataCallback(uint8* data, uint16 len);

1.1.2 实现文件

#include "myso.h"

uint8 GetData(uint8* data, uint16 len){
    std::cout<<"I step into GetData()."<<std::endl;
    return 1;
}

void RecvDataCallback(uint8* data, uint16 len){
    std::cout<<"I step into RecvDataCallback()."<<std::endl;
}

1.2 编译命令

g++ -shared -fPIC myso.cpp -o libmyso.so

  • g++:编译器的名称,它是GCC编译器的C++语言版本。
  • -shared:这个选项告诉编译器生成一个动态链接库,而不是生成可执行文件。动态链接库是一种可以在运行时加载并链接的库,它可以被多个程序共享。
  • -fPIC:这个选项告诉编译器生成位置无关代码(Position Independent Code, PIC),以便动态链接器可以将库加载到任何内存位置上。这对于动态链接库来说是必须的,因为它们可以在不同的程序和不同的地址空间中使用。
  • myso.cpp:要编译的源文件的名称。
  • -o libmyso.so:指定输出文件的名称,这里是libmyso.so。这个名称是动态链接库的约定命名方式,其中lib是前缀,.so是文件扩展名,表示它是一个共享对象(shared object)。

1.3 extern "C"作用

上述源程序中需重点注意myso.h使用了extern "C"修饰函数名称

在每个C++程序(或库、目标文件)中,所有非静态(non-static)函数在二进制文件中都是以“符号(symbol)”形式出现的。这些符号都是唯一的字符串,从而把各个函数在程序、库、目标文件中区分开来。

在C中,符号名正是函数名:strcpy函数的符号名就是“strcpy”。这可能是因为两个非静态函数的名字一定各不相同的缘故。

而C++允许重载(不同的函数有相同的名字但不同的参数),并且有很多C所没有的特性──比如类、成员函数、异常说明──几乎不可能直接用函数名作符号名。为了解决这个问题,C++采用了所谓的name mangling。C++把函数名和一些信息(如参数数量和大小)杂糅在一起,改造成奇形怪状,只有编译器才懂的符号名。例如,被mangle后的foo可能看起来像foo@4%6^,或者,符号名里头甚至不包括“foo”。

C++标准(目前是[ISO14882])并没有定义名字必须如何被mangle,所以每个编译器都按自己的方式来进行name mangling。有些编译器甚至在不同版本间更换mangling算法(尤其是g++ 2.x和3.x)。即使搞清楚了编译器到底怎么进行mangling的,从而可以用dlsym调用函数了,但可能仅仅限于手头的这个编译器而已,而无法在下一版编译器下工作。

extern "C"声明的函数将使用函数名作符号名,该函数就可以像C函数一样被dlopen动态加载。

二、动态库的调用

动态库的调用可分为两种方式:编译链接动态加载

2.1 编译链接

1.在自己的程序中包含动态库的头文件。

  1. 编译自己程序时链接到动态库
    g++ main.cpp -o main -L. -lshared_memory

其中-l表示编译时链接的动态库名称为shared_memory,中间没有空格。

3.运行时添加动态库路径
export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH:

其中$PWD表示把当前路径添加到程序运行时寻找动态库的路径。

优点:可以在main中直接使用动态库的函数。

缺点:要求动态库提供方必须提供头文件且必须重新编译main程序。

2.2 动态加载

动态加载方式不需要重新编译main程序,也不需要在main中添加动态库的头文件。

Linux中通过添加头文件#include <dlfcn.h>提供动态加载动态库的方法。

dlfcn库提供dlopendlsymdlerrordlclose四个函数完成动态加载功能。

2.2.1 dlopen()函数

函数原型:void *dlopen(const char *libname,int flag);

功能描述:dlopen必须在dlerrordlsymdlclose之前调用,表示要将库装载到内存,准备使用。如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。

参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库名称,在dlopen会按照下面的机制去搜寻:

  • 根据环境变量LD_LIBRARY_PATH查找
  • 根据/etc/ld.so.cache查找
  • 查找依次在/lib/usr/lib目录查找。

flag参数表示处理未定义函数的方式,可以使用RTLD_LAZYRTLD_NOW

  • RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内存,等用到没定义的函数再说;
  • RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。

2.2.2 dlsym()函数

函数原型:void *dlsym(void *handle,const char *symbol);

功能描述:dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数。

2.2.3 dlerror()函数

函数原型:char *dlerror(void);

功能描述:dlerror可以获得最近一次dlopendlsymdlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。

2.2.4 dlclose()函数

函数原型:int dlclose(void *);

功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。

2.3 动态加载测试

将第一节中的动态库作为动态加载模块进行测试。

2.3.1 main.cpp

// g++ main.cpp -ldl -o main

#include <dlfcn.h>
#include <iostream>

typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;

typedef int8_t  int8;
typedef int16_t int16;
typedef int32_t int32;

using namespace std;

int main(void)
{
    int get_flag = -1;
    uint16 get_len =2;
    uint8_t get_data[get_len] = {1, 2};

    const char *libname = "./myso.so";
    char *err = nullptr;

    void *handle = dlopen(libname, RTLD_NOW);
    if(!handle){
        std::cout << "Load" << libname << "failed" << dlerror() << std::endl;
        exit(1);
    }

    // 清除错误信息
    dlerror();

    // 调用mysolib动态库的函数
    // 获取GetData函数指针
    typedef uint8 (*pf_GetData) (uint8*, uint16);
    pf_GetData GetData = (pf_GetData)dlsym(handle, "GetData");

    // 获取RecvDataCallback函数指针
    typedef void (*pf_RecvDataCallback) (uint8*, uint16);
    pf_RecvDataCallback RecvDataCallback = (pf_RecvDataCallback)dlsym(handle, "RecvDataCallback");

    //判断是否成功载入函数
    err = dlerror();
    if(err){
        std::cout << "Can't find symbol function" << err << std::endl;
        exit(1);
    }

    GetData(get_data, get_len);
    RecvDataCallback(get_data, get_len);

    //关闭库
    dlclose(handle);
    if(dlerror()){
        std::cout << "Close" << libname << "failed" << dlerror() << std::endl;
        exit(1);
    }
}

2.3.2 编译与运行

编译:
g++ main.cpp -ldl -o main

其中-ldl表示编译时链接到动态库dl,即#include <dlfcn.h>对应的库函数!

运行:
./main

输出:

I step into GetData().
I step into RecvDataCallback().

2.3.3 代码解释

main.cpp通过typedef定义了两个函数指针类型pf_GetDatapf_RecvDataCallback,然后通过这两个函数指针定义了两个函数指针类型的变量GetDataRecvDataCallback,然后通过dlsym()函数从动态库中进行加载函数符号。

    // 获取GetData函数指针
    typedef uint8 (*pf_GetData) (uint8*, uint16);
    pf_GetData GetData = (pf_GetData)dlsym(handle, "GetData");

    // 获取RecvDataCallback函数指针
    typedef void (*pf_RecvDataCallback) (uint8*, uint16);
    pf_RecvDataCallback RecvDataCallback = (pf_RecvDataCallback)dlsym(handle, "RecvDataCallback");

三、动态库中的类如何处理

3.1 含类的动态库

3.1.1 声明文件

// base.h

#ifndef BASE_H
#define BASE_H

class BaseClass {
public:
    virtual void foo() = 0;
};

#endif // BASE_H

3.1.2 实现文件

// base.cpp
#include <iostream>
#include "base.h"
using namespace std;

class DerivedClass : public BaseClass {
public:
    void foo() final {
        cout << "Derived class!" << endl;
    }
};

extern "C" BaseClass* create_object() {
    return new DerivedClass;
}

extern "C" void destroy_object(BaseClass* object) {
    delete object;
}

3.1.3 编译

g++ base.cpp -fPIC -shared -o base.so

3.2 主程序

3.2.1 源码

// basemain.cpp
#include <dlfcn.h>
#include <iostream>
#include "base.h"
using namespace std;

#define CHECK()                                             \
{                                                           \
    const char* dlsym_error = dlerror();                    \
    if (dlsym_error) {                                      \
        cerr << "Cannot load libray or symbol: "            \
             << dlsym_error << '\n';                        \
        exit(1);                                            \
    }                                                       \
}                                                           \

int main() {
    cout << "Start program..." << endl;
    void* handle = dlopen("derived.so", RTLD_LAZY);
    CHECK();

    // "create" is a function pointer
    // argv: ()
    // return: BaseClass*
    BaseClass* (*create)();

    // "destroy" is a function pointer
    // argv: BaseClass*
    // return: void
    void (*destroy)(BaseClass*);

    // explicitly change void pointer to function pointer
    create = (BaseClass* (*)())dlsym(handle, "create_object");
    CHECK();

    destroy = (void (*)(BaseClass*))dlsym(handle, "destroy_object");
    CHECK();

    BaseClass* derived_ptr = (BaseClass*)create();
    derived_ptr->foo();
    destroy(derived_ptr);
}

3.2.2 编译与运行

编译:
g++ basemain.cpp -ldl -o basemain

运行:
./basemain

3.3 输出

Start program...
Derived class!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容