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!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容