插件机制是一种框架,允许开发人员简单地在应用程序中添加或扩展功能。它使广泛使用,因为它可以作为模块被重复使用,并使它们更易于维护和扩展,因此它们在应用程序中非常有用。插件机制允许管理员在需要时轻松安装和卸载插件,而无需对基础应用程序做出更改。

NDD介绍

这里再介绍推荐下优秀的国产软件开源项目 NDD(notepad--)。一个支持windows/linux/mac的文本编辑器,目标是要国产替换同类软件。对比其它竞品Notepad类软件而言,优势是可以跨平台,支持linux mac操作系统。期待国人参与开源,贡献更多有意思的插件。

gitee仓库地址:https://gitee.com/cxasm/notepad--

插件的优势

基于插件的扩展性,进而实现业务模块儿的独立和解耦,增加可维护性和可扩展性。插件使得第三方开发人员可以为系统做增值和拓展工作,也可以使其他开发人员协同开发相互配合,增加新的功能而不破坏现有的核心功能。插件化还能够促进将关注点分开,保证隐藏实现细节,且可以将测试独立开来,并最具有实践意义。

比如强大的Eclipse的平台实际上就是一个所有功能都由插件提供的骨架。Eclipse IDE自身(包括UI和Java开发环境)仅仅是一系列挂在核心框架上的插件。

NDD的插件化实现,是一种很好的范例,让我们看到插件化机制的好处,可以灵活的对软件进行功能拓展,以下对NDD的插件化实现原理做下分析。

NDD插件机制分析

用C++实现插件机制的基本思路是:

一、应用程序(框架)提供出插件接口。

二、由用户或第三方实现这些接口,并编译出相应的动态库(即插件);

三、将所有插件放到某个特定目录,应用程序(框架)运行时会自动搜索该目录,并动态加载目录中的插件。

按照以上思路,分析下NDD源码中的插件机制实现。

插件接口

NDD源码中提供出来的插件接口有两个,接口声明如下:

#define NDD_EXPORT __declspec(dllexport)#ifdef __cplusplusextern "C" {
#endifNDD_EXPORT bool NDD_PROC_IDENTIFY(NDD_PROC_DATA* pProcData);NDD_EXPORT int NDD_PROC_MAIN(QWidget* pNotepad, const QString& strFileName, std::function<QsciScintilla* ()>getCurEdit, NDD_PROC_DATA* procData);#ifdef __cplusplus}
#endif

需要注意,插件接口必须要用extern "C"包含,因为C++的编译器会对程序中符号进行修饰,这个过程在编译器中叫符号修饰(Name Decoration)或者符号改编(Name Mangling)。如果不改为c的方式,那么动态库resolve这种查找入口方式,会找不到句柄handle入口。

以上两个接口,一个是插件的相关说明信息,一个是插件的核心功能实现。

插件实现

NDD_PROC_IDENTIFY接口最简单,就是用来让插件开发者填充插件信息用的。传进来的参数有以下信息:

struct ndd_proc_data
{QString m_strPlugName; //插件名称 必选QString m_strFilePath; //lib 插件的全局路径。必选。插件内部不用管,主程序传递下来QString m_strComment; //插件说明QString m_version; //版本号码。可选QString m_auther;//作者名称。可选int m_menuType;//菜单类型。0:不使用二级菜单 1:创建二级菜单QMenu* m_rootMenu;//如果m_menuType = 1,给出二级根菜单的地址。其他值nullptrndd_proc_data(): m_rootMenu(nullptr), m_menuType(0){}
};typedef struct ndd_proc_data NDD_PROC_DATA;
bool NDD_PROC_IDENTIFY(NDD_PROC_DATA* pProcData)
{if(pProcData == NULL){return false;}pProcData->m_strPlugName = QObject::tr("Hello World Plug");pProcData->m_strComment = QObject::tr("char to Upper.");pProcData->m_version = QString("v1.0");pProcData->m_auther = QString("yangqq.xyz");pProcData->m_menuType = 1;return true;
}
另外一个接口是NDD_PROC_MAIN
这个是插件功能的具体实现接口,插件开发者可在此接口中实现插件的主要功能。
//插件的入口点接口实现
//则点击菜单栏按钮时,会自动调用到该插件的入口点函数接口。
//pNotepad:就是CCNotepad的主界面指针
//strFileName:当前插件DLL的全路径,如果不关心,则可以不使用
//getCurEdit:从NDD主程序传递过来的仿函数,通过该函数获取当前编辑框操作对象QsciScintilla
int NDD_PROC_MAIN(QWidget* pNotepad, const QString &strFileName, std::function<QsciScintilla*()>getCurEdit, NDD_PROC_DATA* pProcData)
{//对于不需要创建二级菜单的例子,pProcData总是nullptr。//该函数每次点击插件菜单时,都会被执行。QsciScintilla* pEdit = getCurEdit();if (pEdit == nullptr){return -1;}//务必拷贝一份pProcData,在外面会释放。if (pProcData != nullptr){s_procData = *pProcData;}s_pMainNotepad = pNotepad;s_getCurEdit = getCurEdit;//做一个简单的转大写的操作QtTestClass* p = new QtTestClass(pNotepad,pEdit);//主窗口关闭时,子窗口也关闭。避免空指针操作p->setWindowFlag(Qt::Window);p->show();return 0;
}

完成了以上这两个接口,编译成动态dll库,其实插件开发就完成啦。如果编译器和使用的QT库同NDD发行版一致,则直接把dll库放入plugin目录即可。接下来看下NDD应用程序是如何加载和使用插件的。

NDD插件加载过程

从ndd应用程序启动到插件加载。过程大致如下:

int main(int argc, char *argv[])
{//可以防止某些屏幕下的字体拥挤重叠问题QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#ifdef Q_OS_MACMyApplication a(argc, argv);
#elseQApplication a(argc, argv);
#endif//......CCNotePad *pMainNotepad = new CCNotePad(true);pMainNotepad->setAttribute(Qt::WA_DeleteOnClose);pMainNotepad->setShareMem(&shared);pMainNotepad->quickshow();a.exec();}
//
//先快速让窗口展示处理,后续再去做复杂的初始化
void CCNotePad::quickshow()
{//......init_toolsMenu();
}
//
void CCNotePad::init_toolsMenu()
{slot_dynamicLoadToolMenu();//connect(ui.menuTools,&QMenu::aboutToShow,this,&CCNotePad::slot_dynamicLoadToolMenu);
}
//动态加载工具菜单项
void CCNotePad::slot_dynamicLoadToolMenu()
{//......
#ifdef NO_PLUGIN//动态加载插件m_pluginList.clear();loadPluginLib();
#endif
}

插件的加载过程在loadPluginLib()函数中,进入到plugin目录中加载插件。

#ifdef NO_PLUGIN
void CCNotePad::loadPluginLib()
{QString strDir = qApp->applicationDirPath();QDir dir(strDir);if (dir.cd("./plugin")){strDir = dir.absolutePath();loadPluginProcs(strDir,ui.menuPlugin);}
}

foundCallback回调函数接口,找到插件信息后 在onPlugFound函数中处理,完成与界面菜单的绑定。

void CCNotePad::loadPluginProcs(QString strLibDir, QMenu* pMenu)
{std::function<void(NDD_PROC_DATA&, QMenu*)> foundCallBack = std::bind(&CCNotePad::onPlugFound, this, std::placeholders::_1, std::placeholders::_2);int nRet = loadProc(strLibDir, foundCallBack, pMenu);if (nRet > 0){ui.statusBar->showMessage(tr("load plugin in dir %1 success, plugin num %2").arg(strLibDir).arg(nRet));}
}

在点击菜单后触发执行onPlugWork,如果设置的有启用二级菜单,则初始化设置二级菜单。

void CCNotePad::onPlugFound(NDD_PROC_DATA& procData, QMenu* pUserData)
{QMenu* pMenu = pUserData;if (pMenu == NULL){return;}//创建actionif (procData.m_menuType == 0){QAction* pAction = new QAction(procData.m_strPlugName, pMenu);pMenu->addAction(pAction);pAction->setText(procData.m_strPlugName);pAction->setData(procData.m_strFilePath);connect(pAction, &QAction::triggered, this, &CCNotePad::onPlugWork);}else if (procData.m_menuType == 1){//创建二级菜单QMenu* pluginMenu = new QMenu(procData.m_strPlugName, pMenu);pMenu->addMenu(pluginMenu);//菜单句柄通过procData传递到插件中procData.m_rootMenu = pluginMenu;sendParaToPlugin(procData);}else{return;}// 暂存加载到的插件信息m_pluginList.append(procData);
}
//把插件需要的参数,传递到插件中去
void CCNotePad::sendParaToPlugin(NDD_PROC_DATA& procData)
{QString plugPath = procData.m_strFilePath;QLibrary* pLib = new QLibrary(plugPath);NDD_PROC_MAIN_CALLBACK pMainCallBack;pMainCallBack = (NDD_PROC_MAIN_CALLBACK)pLib->resolve("NDD_PROC_MAIN");if (pMainCallBack != NULL){std::function<QsciScintilla* ()> foundCallBack = std::bind(&CCNotePad::getCurEditView, this);pMainCallBack(this, plugPath, foundCallBack, &procData);}else{ui.statusBar->showMessage(tr("plugin %1 load failed !").arg(plugPath), 10000);}
}
//真正执行插件的工作
void CCNotePad::onPlugWork(bool check)
{QAction* pAct = dynamic_cast<QAction*>(sender());if (pAct != nullptr){QString plugPath = pAct->data().toString();QLibrary* pLib = new QLibrary(plugPath);NDD_PROC_MAIN_CALLBACK pMainCallBack;pMainCallBack = (NDD_PROC_MAIN_CALLBACK)pLib->resolve("NDD_PROC_MAIN");if (pMainCallBack != NULL){std::function<QsciScintilla* ()> foundCallBack = std::bind(&CCNotePad::getCurEditView, this);pMainCallBack(this, plugPath, foundCallBack, nullptr);}else{ui.statusBar->showMessage(tr("plugin %1 load failed !").arg(plugPath), 10000);}}
}

虽然以上过程看似复杂一点儿,其实关键调用就是拿到函数指针,然后根据需要做些处理。插件信息存储在QList<NDD_PROC_DATA> m_pluginList。有个界面对这个信息进行展示。

void  CCNotePad::slot_pluginMgr()
{
#ifdef NO_PLUGINPluginMgr* pWin = new PluginMgr(this, m_pluginList);pWin->setAttribute(Qt::WA_DeleteOnClose);pWin->show();
#elseQMessageBox::warning(this, "info", u8"便携版本不支持插件,请下载插件版!");
#endif
}

为防止中文乱码,支持中文的方法是文件编码保存为utf-8格式。 输入汉字如上写法,u8"中文字符"。编译脚本指定如下:

# win下需要开启UNICODE进行支持TCHAR
if(CMAKE_HOST_WIN32)add_definitions(-D_UNICODE -DUNICODE)
endif()

plugin机制的关键,既定义函数指针,拿到函数指针,使用函数指针。

typedef bool (*NDD_PROC_IDENTIFY_CALLBACK)(NDD_PROC_DATA* pProcData);
typedef void (*NDD_PROC_FOUND_CALLBACK)(NDD_PROC_DATA* pProcData, void* pUserData);
#include "plugin.h"
#include <QLibrary>
#include <QDir>
#include <QMenu>
#include <QAction>bool loadApplication(const QString& strFileName, NDD_PROC_DATA* pProcData)
{QLibrary lib(strFileName);NDD_PROC_IDENTIFY_CALLBACK procCallBack;procCallBack = (NDD_PROC_IDENTIFY_CALLBACK)lib.resolve("NDD_PROC_IDENTIFY");if (procCallBack == NULL){return false;}if (!procCallBack(pProcData)){return false;}pProcData->m_strFilePath = strFileName;return true;
}int loadProc(const QString& strDirOut, std::function<void(NDD_PROC_DATA&, QMenu*)> funcallback, QMenu* pUserData)
{int nReturn = 0;QStringList list;QDir dir;dir.setPath(strDirOut);QString strDir, strName;QStringList strFilter;strDir = dir.absolutePath();strDir += QDir::separator();
#if  defined(Q_OS_WIN)strFilter << "*.dll";
#elsestrFilter << "lib*.so";
#endiflist = dir.entryList(strFilter, QDir::Files | QDir::Readable, QDir::Name);QStringList::Iterator it = list.begin();for (; it != list.end(); ++it){NDD_PROC_DATA procData;strName = *it;strName = strDir + strName;if (!loadApplication(strName, &procData)){continue;}funcallback(procData, pUserData);nReturn++;}return nReturn;
}

c++插件化 NDD源码的插件机制实现解析相关推荐

  1. 唯一插件化Replugin源码及原理深度剖析--插件的安装、加载原理

    上一篇 唯一插件化Replugin源码及原理深度剖析–唯一Hook点原理 在Replugin的初始化过程中,我将他们分成了比较重要3个模块,整体框架的初始化.hook系统ClassLoader.插件的 ...

  2. vue开源项目(各大插件,gitup源码)

    vue开源项目(各大插件,gitup源码) 目录 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 UI组件 element★31142 - 饿了么出品的Vue2的web UI工具 ...

  3. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

  4. 基于openfire源码开发插件

    [0]README 1)本文旨在 简述如何 基于openfire源码开发插件, 如何导入 openfire源码到 eclipse,参见 http://blog.csdn.net/pacosonswjt ...

  5. 易支付系统源码_刷脸支付系统源码,插件源码合作模式有哪些,采购源码需要注意什么...

    对刷脸支付比较关注的朋友,应该都知道源码.当拥有这个,就意味着有了独立的系统.也意味着可以打造自己的品牌,转化自己资源,获取更多的利润.但是想拥有一套源码也是不简单的,不仅因为编写的难度和价格,也因为 ...

  6. 明月浩空播放器php源码,Emlog插件-明月浩空音乐播放器

    源码预览 源码介绍 明月浩空-Html5浮窗音乐播放器是基于QQ音乐.酷狗音乐.网易云音乐等歌曲ID全自动解析的Html5音乐播放器 程序依靠服务器强大的接口功能,只需要一个歌曲ID既可获取歌曲全部信 ...

  7. 漂亮的Emlog博客网站模板源码+附插件合集

    正文: 一款非常不错的博客源码,东西非常齐全,网站源码+模板+插件全部都有,还带有广告位功能以及各种人性化小插件,音乐播放插件等,有需要的自行去体验吧. 程序: wwesd.lanzoub.com/i ...

  8. html全屏背景视频特效,HTML5全屏背景视频特效插件Vidage.js源码

    下面我们对HTML5全屏背景视频特效插件Vidage.js源码文件阐述相关使用资料和HTML5全屏背景视频特效插件Vidage.js源码文件的更新信息. HTML5全屏背景视频特效插件Vidage.j ...

  9. 【Android 插件化】Hook 插件化框架 ( 通过反射获取 “插件包“ 中的 Element[] dexElements )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

最新文章

  1. 6-flutter 状态管理
  2. 如何赋予自主系统具备持续学习的能力?
  3. mysql大于等于怎么写_MySQL 对于千万级的大表要怎么优化?我写了6000字的深度解读...
  4. MarathonLb的负载研究
  5. 【Linux学习010】算数运算、文件测试、字符测试、位置变量和特殊变量
  6. keyshot怎么贴logo_KeyShot实例渲染技巧教程,教你如何给产品添加有织纹的Logo
  7. 【知识图谱 赵军 学习笔记】第五章 实体消歧
  8. 外企常见英语口语面试题
  9. 疲劳检测——眨眼检测
  10. Android使用xml自定义软键盘效果(附源码)
  11. 微信公众平台之CURL应用access_token
  12. 虚拟机的桥接模式和NAT模式
  13. Python实现地图四色原理的遗传算法(GA)着色实现
  14. C语言进阶第15式:逻辑运算符分析
  15. [libxml2]_[C/C++]_[使用libxml2读取分析xml文件]
  16. 腾讯云使用phpStudy部署网站(附腾讯云优惠券)
  17. 新概念c语言周二强07答案,新概念C语言能力教程(普通高等教育十二五规划教材)...
  18. ★ .net应用程序如何表现XP风格?
  19. golang初始化数据库(MySQL)
  20. (已更新)谁是卧底线下发牌器微信小程序源码下载,强大的自定义功能

热门文章

  1. ARC120D Bracket Score 2 (模拟)
  2. [荐]水浒调兵遣将的玄机(发展期,连载九)
  3. 阳明同调(琴语言第1讲专用领域语言基础)
  4. 2023届-多益网络(提前批)-软件开发-凉经
  5. 江苏省计算机二级vb百度网盘,江苏省计算机二级vb基本知识点总结
  6. 微风PS教程:人像修图
  7. 这么给力?台积电7nm ARM处理器频率可冲4GHz
  8. 自定义TAG标签页面聚合
  9. 企业级开发框架---Django(一)
  10. html是什么课程,【HTMLCSS 课程01 】HTML到底是个啥?