C++动态加载so/dll库的实现

 更新时间:2023年07月23日 09:28:50   作者:QX0  
本文主要介绍了C++动态加载so/dll库的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

在C++使用动态库,(linux下是.so,windows下是.dll) 比较常见的方式是在编译时,直接连接到程序中。但是除了这种方式外,还可以使用的动态加载的方式去使用动态库。

两种方式的区别

  • 在编译时把库连接到程序:这种方式是在编译的时候,就确定了要链接的库文件,然后通过编译参数在链接时直接把动态库的地址空间等等信息连接到程序中。程序在运行时,可以直接根据路径去寻找动态库,然后加载到程序中,然后运行,这种方式在日常开发中用的比较多。
  • 在程序运行时动态加载库:这种方式是在程序运行时,通过调用系统函数,把动态库加载到程序中,然后执行动态库中的代码。这种方式和编译时链接的优势是可以在程序运行的过程中动态加载和卸载库。可以在不修改源程序的前提下,使用新的库。这种方式,比较常见的应用是程序的插件系统。

动态加载库

不废话了,直接开始上代码

在程序运行的过程中动态加载库,需要依赖操作系统,所以在不同的系统上有不同的系统调用函数。

在linux 上需要用到 dlopen 函数加载库,dlclose 函数释放库,dlsym 函数 查找库函数

需要的头文件 #include <dlfcn.h>

在windows 上需要 LoadLibrary 宏加载库,FreeLibrary 宏释放库,GetProcAddress 函数查找库函数

需要的头文件 #include <windows.h>

基类功能

在C++中可以通过定义一个抽象类来作为所有库的基类,所有的库文件都实现这个基类,然后重写基类的纯虚函数。可以在加载到所有库后,都可以把库里的类作为抽象类的派生类。

先定义一个基类 base.h

#ifndef DLOAD_BASE_H
#define DLOAD_BASE_H
/**
 * 必须实现 moduleName_create 函数,来初始化对象
 * extern "C" Base *module1_create() {
 *     return new Module;
 * }
 *
 * //必须实现 moduleName_destroy 函数,来回收对象
 * extern "C" void module1_destroy(Base *obj) {
 *     delete obj;
 * }
 */
class Base {
 public:
  virtual std::string readLine(const std::string &) = 0;
  virtual ~Base() = default;
};
#endif //DLOAD_BASE_H

这个基类的功能很简单,只有一个纯虚函数readLine 这个函数会传入一个字符串,然后返回一个字符串

注释中的哪两个函数,后面会有详细的介绍

实现一个模块

可以把一个库看做是一个模块,现在实现一个模块

//简单的模块 例子
//转大写
#include <algorithm>
#include <string>
#include "../base.h"
class Module1 : public Base {
  std::string readLine(const std::string &str) override {
      std::string str2(str);
      std::transform(str.begin(), str.end(), str2.begin(), ::toupper);
      return str2;
  }
};
//必须实现 moduleName_create 函数,来初始化对象
extern "C" Base *module1_create() {
    return new Module1;
}
//必须实现 moduleName_destroy 函数,来回收对象
extern "C" void module1_destroy(Base *obj) {
    delete obj;
}

这个功能非常简单,把传入的字符串转成大写,然后返回

为什么需要 Base *module1_create() 和 void module1_destroy(Base *obj) 这两个函数

因为在把库加载完成后,需要使用库里的函数,但是不能直接查找C++的类,然后再初始化对象,只能在库里完成C++对象的初始化,然后返回对象的指针。
所以需要在库里有对应的函数来初始化对象和回收对象,所以就有了这两个函数。

为什么要 extern "C"

因为C++有函数重载的功能,所以编译器在编译代码的时候,会对函数重命名。但是对函数重命名的规则,没有统一的标准,不同编译器有不同的规则。像 module1_create 这个函数可能就被重命名成 _Z14module1_create这样的字符串。这样后面使用 dlsym 或者 GetProcAddress 函数查找库里的函数时,就没法找到对应的函数了。所以使用extern "C" 让编译器使用C的规则来编译这段函数

至于这两个函数的名字 module1_create 和 module1_destroy 没有强制的要求,但是要有一定的规范。否则在加载到库后,没法根据函数名查找到对应的函数。这里用到的规则是 模块名_create 和 模块名_destroy

加载库

下面开始加载库,因为在同的系统下,加载库调用的函数不同,所以使用 宏来完成不用系统下的条件编译,最终完成加载库

//声明创建对象的函数
typedef Base *(*create)();
//声明回收对象的函数
typedef void (*destroy)(Base *);
//调用系统函数,加载动态库
#ifdef _WIN32
HINSTANCE loadLib(Base **base, const char *path, const char *funName) {
    auto handle = LoadLibrary(path);
    if (!handle) {
        return nullptr;
    }
    auto cr = (create) GetProcAddress(handle, funName);
    if (cr) {
        *base = cr();
    }
    return handle;
}
//调用系统函数,卸载动态库
void freeLib(HINSTANCE handle, Base *obj, const char *funName) {
    auto free = (destroy) GetProcAddress(handle, funName);
    if (free) {
        free(obj);
    }
    FreeLibrary(handle);
}
#else
void *loadLib(Base **base, const char *path, const char *funName) {
    auto handle = dlopen(path, RTLD_LAZY);
    if (!handle) {
        return nullptr;
    }
    auto cr = (create) dlsym(handle, funName);
    if (cr) {
        *base = cr();
    }
    return handle;
}
//调用系统函数,卸载动态库
void freeLib(void *handle, Base *obj, const char *funName) {
    auto free = (destroy) dlsym(handle, funName);
    if (free) {
        free(obj);
    }
    dlclose(handle);
}
#endif

在代码最开始的位置,通过 typedef 声明了两个函数的指针,在查找到函数后,把函数强转成对应的类型,才能在后面使用

使用库

int main() {
    std::string libPath;
#ifdef _WIN32
    libPath = std::string("./module/libmodule1" + ".dll");
#else
    libPath = std::string("./module/libmodule1" + ".so");
#endif
    Base *module = nullptr;
    auto handle = loadLib(&module, libPath.c_str(), std::string("module1_create").c_str());
    if (!module) {
        std::cout << "load lib module1" << " fail" << std::endl;
        return 1;
    }
    std::cout << module->readLine("abc") << std::endl;
    return 0;
}

现在基本就完成了一个动态库的动态加载过程。如果想要拓展,只要再按照这个规则,写一个新的模块然后加载上来就可以了。
最后放一个相对完整的动态加载的demo,github

到此这篇关于C++动态加载so/dll库的实现的文章就介绍到这了,更多相关C++动态加载so/dll库内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言二叉树的非递归遍历实例分析

    C语言二叉树的非递归遍历实例分析

    这篇文章主要介绍了C语言二叉树的非递归遍历,包括了先序遍历、中序遍历与后序遍历,需要的朋友可以参考下
    2014-09-09
  • 详解C++中的const和constexpr

    详解C++中的const和constexpr

    这篇文章主要为大家介绍了C++中的const和constexpr ,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2021-12-12
  • C++中实现队列类链式存储与栈类链式存储的代码示例

    C++中实现队列类链式存储与栈类链式存储的代码示例

    这篇文章主要介绍了C++中实现队列类链式存储与栈类链式存储的代码示例,通过注释来说明,直接上代码,简单粗暴XD 需要的朋友可以参考下
    2016-03-03
  • MATLAB中subplot函数的语法与使用实例

    MATLAB中subplot函数的语法与使用实例

    subplot()是将多个图画到一个平面上的工具,下面这篇文章主要给大家介绍了关于MATLAB中subplot函数的语法与使用的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-08-08
  • C/C++产生指定范围和不定范围随机数的实例代码

    C/C++产生指定范围和不定范围随机数的实例代码

    C/C++产生随机数用到两个函数rand() 和 srand(),这里介绍不指定范围产生随机数和指定范围产生随机数的方法代码大家参考使用
    2013-11-11
  • C++共享智能指针shared_ptr的实现

    C++共享智能指针shared_ptr的实现

    在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露,解决这个问题最有效的方法是使用智能指针,本文主要介绍了C++共享智能指针shared_ptr的实现,感兴趣的可以了解一下
    2023-12-12
  • C++11 上下文关键字的具体实践

    C++11 上下文关键字的具体实践

    本文主要介绍了C++11 上下文关键字的具体实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • C++模拟实现string的方法详解

    C++模拟实现string的方法详解

    标准库类型string表示可变长的字符序列,使用string类型必须首先包含string的头文件。本文将利用C++模拟实现string,需要的可以参考一下
    2022-11-11
  • M1 Macbook vscode C++ debug调试实现

    M1 Macbook vscode C++ debug调试实现

    本文主要介绍了M1 Macbook vscode C++ debug调试,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • C语言去除相邻重复字符函数的实现方法

    C语言去除相邻重复字符函数的实现方法

    这篇文章主要介绍了C语言去除相邻重复字符函数的实现方法的相关资料,实现去重字符串相邻重复的字符,不相邻的不用去重的功能,需要的朋友可以参考下
    2017-08-08

最新评论