C++ Plug-in 技术的一些深度思考(1)
我们可以使用动态连接库的技术实现上诉的功能。大方向说,将CalcAdd和CalcMul编译连接成动态连接库,CalcFactory.cpp动态的加载它们。这样的话,可以实现两个功能:
[1] 如果更新libCalcAdd.so或者增加一个libCalcMin.so,不需要重新编译,链接,发布主程序calc,只需要重新发布你更新或者增加的so文件
[2] 如果我们可以横向的定义出主程序的测试规范,我们甚至不需要回归测试主程序calc。
我们重新设计了文件目录的组织结构,如下图所示:
有修改的文件有CalcFactory.cpp / CalcFactory.h。主要是增加了load 动态连接库的内容。 CalcAdd.cpp和CalcMul.cpp分别增加了一个C函数,返回一个动态分配的CalcAdd或者CalcMul。
// core/src/CalcFactory.cpp
#include <dlfcn.h> //for dlopen and dlsym
#include <iostream>
#include "../include/CalcFactory.h"
using std::cerr;
using std::endl;
using std::string;
using std::map;
map<string, CalcFactory::GetNewInstanceFun> CalcFactory::calcHandlers;
ICalc* CalcFactory::getCalc(string calcType)
{
if (calcHandlers.end() == calcHandlers.find(calcType)) {
string libName = "../lib/libCalc" + calcType + ".so";
void *pdlHandle = dlopen(libName.c_str(), RTLD_NOW);
if(!pdlHandle)
{
cerr << "Fail to load " << libName << endl;
return 0;
}
char *err = dlerror();
if(err)
{
cerr << "Fail to load " << libName << " due to " << err << endl;
return 0;
}
void *rst = dlsym(pdlHandle, "getNewInstance");
if (!rst) {
cerr << "There is no \"getNewInstance\" function in " << libName << endl;
return 0;
}
GetNewInstanceFun newFun = (GetNewInstanceFun)rst;
if (!newFun) {
cerr << "Fail to cast to (ICalc *)(*)(). " << endl;
return 0;
}
err = dlerror();
if(err)
{
cerr << "There is no \"getNewInstance\" function in " << libName
<< " due to " << err <<endl;
return 0;
}
calcHandlers[calcType] = newFun;
}
GetNewInstanceFun nf = calcHandlers[calcType];
return nf();
}
注意这里的修改。
[1] 取消了这个cpp对CalcAdd.h和CalcMul.h的依赖。本质上,在Core这边里对Plug-in的使用都是完全基于接口ICalc的。
[2] 增加了一个全局的map,用来避免反复的load动态连接库。
[3] 在Unix动态连接的思路就是dlopen和dlsym,具体的技术大家可以google一下。如果有Windows的经验,可以发现这些过程Windows和Unix几乎是一致的。
// plugin/src/CalcAdd.cpp
#include "../include/CalcAdd.h"
void CalcAdd::calc(int *p, size_t size, int *result)
{
for (size_t i = 0 ; i < size; ++i)
{
(*result) += p[i];
}
}
CalcAdd::~CalcAdd() { }
extern "C"
{
ICalc *getNewInstance()
{
return new CalcAdd;
}
}
注意了,其实整个类CalcAdd.cpp的实现没有任何的改变。改变的是增加了一个函数ICalc *getNewInstance()。这个函数要注意几点:
[1] extern “C” 在这里不能省略。也许你运气好,在你的平台上省略这个东西能顺利运行通过。但是这种代码绝对不能移植,至少在我的Mac上面,不能省略这个extern "C"。本质原因是因为这个函数要被动态加载,而dlsym几乎要返回的那个函数指针是C链接的;
[2] getNewInstance()这个函数的签名,包括返回类型ICalc*, 参数个数和类型,函数名getNewInstance是不能乱改的。首先,函数名getNewInstance被CalcFactory.cpp的dlsym用作找到函数指针的索引,void *rst = dlsym(pdlHandle, "getNewInstance"); 如果函数名错了,那么这个返回的rst是NULL。如果函数签名不对,那么GetNewInstanceFun newFun = (GetNewInstanceFun)rst; 会出错。而且这种错误不容易检查,也许newFun并不是NULL。所以一旦这个Core定下来了,所有的Plug-in里面都必须是这样的函数:
extern "C"
{
ICalc *getNewInstance()
{
// do something
}
}
当然不用太担心,如果你写错了这个函数,你一测试就能发现。
其实,还有的修改就是Makefile。这个不同的平台,不同的编译器选项差别很大。我大致介绍一下:
[1] Solaris 的 CC:生成libCalcAdd.so的时候,需要使用-G参数。生成使用so的主程序需要使用-ldl
[2] Mac的g++ : 生成libCalcAdd.so的时候,需要使用-dynamiclib -flat_namespace。生成 使用so的主程序不需要使用另外的选项
[3] Linux的g++ : 生成libCalcAdd.so的时候,需要使用-fPIC -shared。生成使用so的主程序需要使用-ldl.
大家可以下载v3.tar.bz2查看源代码和Makefile。需要先进入Core 中 make -f Makefile.(mac|sun|lnx) core
然后进入plugin中 make -f Makefile.(mac|sun|lnx) plugin
然后在主目录的bin下面运行calc就可以了。
我们还可以试一下,在plugin里面加入一个CalcMax的类,找出数组中的最大值。当我们把这个libAddMax.so放到lib之后,我们主程序就可以运行成calc Max了。
这样返回类的指针的设计非常的不错,首先,如果你原有的系统,很可能CalcAdd和CalcMul就是类。这样你几乎不需要修改什么代码;其次,如果你的类CalcAdd也许没有实现那个calc接口,没关系,用Adaptor模式适配一下。当然了,也许你会觉得这里的算法类CalcAdd应该是单件啊,不应该每次都去new。这是因为有的系统,现存的CalcAdd里面有很多pirvate的变量和函数,而且这些函数都不是线程安全的。如果你改成单件类,那么使用过程中你的程序也会线程不安全。至少我们现在的系统就是这样的。所以我选择每次返回一个对象。
使用动态库不错,可以完全做到plug-in系统。而且未来完全可以做到热启动,热加载。但是我们先停一停,在下一节,我将给出一种完全不一样的实现plug-in的机制。
- 大小: 16.1 KB
分享到:
相关推荐
IDA_plug-in_writing_in_C_or_C++, 中文和英文两个。IDA_plug-in_writing_in_C_or_C++
《GNU ARM Eclipse Plug-in:为Eclipse开发环境增添ARM支持》 GNU ARM Eclipse Plug-in是专为Eclipse集成开发环境(IDE)设计的一款扩展插件,由开发者ilg-ul和justxi共同贡献。该插件的主要目的是为了在Eclipse...
Bridging the gap from theory to programming, Designing Software Synthesizer Plug-Ins in C++ For RackAFX, VST3 and Audio Units contains complete code for designing and implementing software ...
开发者使用Visual C++编写的应用程序,通常会依赖于一些特定的库文件,这些库文件在用户计算机上可能并未预装。因此,Visual C++ Redistributable就应运而生,它的主要功能就是提供这些必要的运行时库,使得用户无需...
2. "Microsoft Visual C++ 2015-2019 Redistributable - 14.26.28720.3 x86.exe" 是32位版本的安装程序,同样提供版本号为14.26.28720.3的运行库,适用于32位Windows系统或在64位系统上运行32位程序。 安装这两个...
《S60 Platform ECom Plug-in Examples v2.0_en:Symbian手机程序开发实践指南》 在移动操作系统领域,Symbian以其强大的功能和广泛的设备支持,曾一度成为智能手机开发的重要平台。"S60 Platform ECom Plug-in ...
warning: gcc-c++-4.4.7-3.el6.x86_64.rpm: Header V3 RSA/SHA1 Signature, key ID c105b9de: NOKEY error: Failed dependencies: gcc = 4.4.7-3.el6 is needed by gcc-c++-4.4.7-3.el6.x86_64 libstdc++ = 4.4.7-...
C++编程思想-Thinking in C++
gcc-c++-3.4.6-3.1.x86_64.rpm
-Giving guidance on how to use Templates in Modern C++ -For programmers, who just use templates, who provide class and function templates, and who provide generic and foundation libraries -Covering ...
NACL,全称Native Client,是Google推出的一个技术,允许开发者编写C或C++代码,然后在浏览器沙箱环境中运行,以实现高性能的Web应用。NACL的目标是让Web应用具备接近原生应用的速度和功能。在Visual Studio中添加...
这里,你已经有一个名为"Adobe Flash Builder 4.5 Plug-in Utility.exe"的文件,这是一个离线安装程序。以下是离线安装步骤: 1. **运行安装程序**:双击"Adobe Flash Builder 4.5 Plug-in Utility.exe",按照向导...
基于libmodbus开源库 C++ modbus-rtu通信测试程序源码,vs2013平台。此文件为主站程序可实现与从站(从站可以使用Modbus Slave 仿真软件)通信,实现寄存器的读写功能。
Linux安装Oracle先决条件gcc-c++-3.4.6-8.x86_64.rpm
《C++ Templates - The Complete Guide》是一本深入探讨C++模板技术的专业书籍,它为开发者提供了全面、详尽的模板知识。模板是C++语言中一个强大的特性,它允许我们编写泛型代码,提高代码的复用性和灵活性。这本书...
Object-Oriented Programming in C++Object-Oriented Programming in C++Object-Oriented Programming in C++
compat-gcc-c++-7.3-2.96.128.i386.rpm
gcc-c++-4.4.7-4.el6.i686.rpm cpp-4.4.7-4.el6.i686.rpm gcc-4.4.7-4.el6.i686 .rpm libstdc++-devel-4.4.7-4.el6.i686.rpm libstdc++-4.4.7-4.el6.i686.rpm
compat-gcc-c++-7.3-2.96.122.i386.rpm
【标题】"基于树莓派Qt+opencv+yolov5-Lite+C++部署深度学习推理"涉及的关键技术点包括树莓派开发、Qt图形界面框架、OpenCV图像处理库、C++编程语言以及轻量级的YOLOv5深度学习模型。 首先,树莓派是一种低成本、...