动态加载动态库,及库内c风格方法

2023/12/21

Tags: C++

Table of Contents

1 背景

《新的网口相机软件工程》,设计之初就考虑到扩展性,对于不同产品,不同的项目来说,

代码全都收敛到同一个代码库,同一个分支。不同的产品也只不过是不同的插件而已,

可以通过配置文件中的项目类型,过滤不需要加载的插件

这样做的优点是,

2 遇到什么问题

算法库,按产品划分分支, 不同的产品,有不同的头文件

这导致一个问题

当软件与不同的算法产品搭配使用时,按以往的方式,添加头文件,链接算法库, 由于算法头文件不同,将导致软件也需要开和算法一样多的分支才能保证链接算法库不会因为找不到符号导致编译失败

这样一来,软件也不得不每一个定制项目开一个分支去和算法适配了。

3 解决方法

我曾向算法部沟通建议过,让他们给出一个合并版本的头文件, 不同的产品,按名称空间划分好,例如二维码算法项目有自己的接口,也有3d激光线扫的接口,但3d激光线扫的接口只放空实现, 这个建议完驳回了,因为算法认为这样会污染头文件

所以我尝试了另一种动态库调用的方式, 通过系统api 动态加载动态库,并查找已知的符号,转换为函数指针, 调用函数指针。

经测试, windows上和linux上都可以正常的调用,不过需要算法提供C风格的函数导出。

4 示例代码

4.1 纯c风格导出

头文件

purecpplib.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef PURECPPLIB_H
#define PURECPPLIB_H

#ifdef _WIN32
#define HBBEXPORT __declspec(dllexport)
#else
#define HBBEXPORT
#endif

#ifdef __cplusplus
extern "C"
{
#endif

    HBBEXPORT int avg(int a, int b);

#ifdef __cplusplus
}
#endif

#endif // PURECPPLIB_H

使用__cplusplus这个宏加了一对

1
2
3
extern "C"
{
}

把想导出的方法包住,当然windows下仍然要加导出的修饰符__declspec(dllexport)

CPP

purecpplib.cpp

1
2
3
4
5
#include "purecpplib.h"
int avg(int a, int b)
{
    return (a + b) / 2;
}

4.2 windows/linux 动态调用简单封装

头文件

danymic_lib_caller.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#ifndef DANYMIC_LIB_CALLER_H
#define DANYMIC_LIB_CALLER_H
#include "prism_qt_modular_global.h"

#ifdef _WIN32
#include <Windows.h>
#endif

#ifdef __linux
#include <dlfcn.h>
#endif

#include <iostream>
#include <filesystem>


class PRISM_QT_MODULAR_EXPORT dynamic_lib_caller
{
public:
    dynamic_lib_caller();

    //加载动态库
    static void* loadLib(std::string path);
    //通过符号获取函数指针
    static void* getFunctionAddr(void *lib, const char*  funName);
    //卸载动态库
    static void  unloadLib(void *lib);
};
#endif // DANYMIC_LIB_CALLER_H

CPP

danymic_lib_caller.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include "include/prism/qt/modular/dynamic_lib_caller.h"
#include <sstream>

dynamic_lib_caller::dynamic_lib_caller()
{

}

#ifdef _WIN32
void *dynamic_lib_caller::loadLib(std::string path)
{
    HMODULE lib = LoadLibrary(path.c_str()) ;
    return static_cast<void*>(lib);
}

void *dynamic_lib_caller::getFunctionAddr(void *lib, const char*  funName)
{
    HMODULE library = static_cast<HMODULE>(lib);
     // 获取函数地址
    FARPROC functionAddr = GetProcAddress(library, funName);
    if (!functionAddr) {
        return nullptr;
    }
    return  static_cast<void*>(functionAddr);

}

void dynamic_lib_caller::unloadLib(void *lib)
{
    if(lib)
    {
        HMODULE library = static_cast<HMODULE>(lib);
        FreeLibrary(library);
    }
}

#endif

#ifdef __linux
void *dynamic_lib_caller::loadLib(std::string path)
{
    void* library = dlopen(path.c_str(), RTLD_NOW);
    return library;
}

void *dynamic_lib_caller::getFunctionAddr(void *lib, const char*  funName)
{
    void* functionAddr = dlsym(lib, funName);
     // 获取函数地址
    if (!functionAddr)
    {
        return nullptr;
    }
    return  functionAddr;

}

void dynamic_lib_caller::unloadLib(void *lib)
{
    if(lib)
    {
        dlclose(lib);
    }
}

#endif

这个cpp文件通过 _WIN32__linux 两个编译器自带的宏,兼容了windows和linux下的 动态加载库,获取函数指针,卸载动态库的方法.

4.3 使用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <prism/qt/modular/dynamic_lib_caller.h>
#include <catch2/catch_all.hpp>
#include <catch2/catch_all.hpp>
#define CATCH_CONFIG_RUNNER




TEST_CASE("动态库调用")
{
#ifdef _WIN32
    void* lib =dynamic_lib_caller::loadLib(R"(...\pureCppLib.dll)");
#else
    void* lib =dynamic_lib_caller::loadLib(R"(.../libpureCppLib.so.1.0.0)");
#endif

    if(lib)
    {
        //[1] 查找pureCppLib动态库中的avg函数地址
        void* func = dynamic_lib_caller::getFunctionAddr(lib,"avg"); 
        if(func)
        {
            double value = -1;
            typedef int (*avgFuc)(int, int); // [2] 转换为函数指针
            avgFuc f = (avgFuc)(func);

            if(f)
            {
                value = f(6,15); // [3] 调用

                dynamic_lib_caller::unloadLib(lib);
            }

            std::cout << value;
        }
    }


}



int main(int argc, const char** argv)
{
#ifdef _MSC_VER
    SetConsoleOutputCP(CP_UTF8);
    SetConsoleCP(CP_UTF8);
#endif
    int result = Catch::Session().run(argc, argv);
    return result;
}

在windows和linux下测试,函数都正常输出10, (6和15)的平均值

如此一下,只需要算法给出c风格的接口,我们就能在不链接的情况下调用了。

甚至可以在有gcc的环境,使用字符串写一个c++的方法,用命令行调用gcc编译后动态的加载到程序里直接调用,达到类似脚本的目的

>> Home

Comments