C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++动态加载so/dll库

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

作者: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库内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文