1 背景
《新的网口相机软件工程》,设计之初就考虑到扩展性,对于不同产品,不同的项目来说,
代码全都收敛到同一个代码库,同一个分支。不同的产品也只不过是不同的插件而已,
可以通过配置文件中的项目类型,过滤不需要加载的插件
这样做的优点是,
- 可以像搭积木一样,从各个动态库中取出UI和逻辑, 拼凑成一个app
- 减少重复的代码,提高代码重用率
- 减少维护成本,bug的修复,只需要一次,不需要跨分支,跨产品的到处合fix分支和补丁
- 减少代码分支数量,所有使用框架的人围绕着master分支展开工作
- 方便进行重构,好的代码都是重构出来的,当所有代码都在事一个分支上时,重构时只要把旧的接口保留,并标记为过时,增加新的重构方法,慢慢迭代掉过时方法就行了,如果分支很多的话,重构往往阻力重重
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
这个宏加了一对
把想导出的方法包住,当然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