<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
外掛機制是一種框架,允許開發人員簡單地在應用程式中新增或擴充套件功能。它使廣泛使用,因為它可以作為模組被重複使用,並使它們更易於維護和擴充套件,因此它們在應用程式中非常有用。外掛機制允許管理員在需要時輕鬆安裝和解除安裝外掛,而無需對基礎應用程式做出更改。
這裡再介紹推薦下優秀的國產軟體開源專案 NDD(notepad--)。一個支援windows/linux/mac的文字編輯器,目標是要國產替換同類軟體。對比其它競品Notepad類軟體而言,優勢是可以跨平臺,支援linux mac作業系統。期待國人蔘與開源,貢獻更多有意思的外掛。
gitee倉庫地址:https://gitee.com/cxasm/notepad--
基於外掛的擴充套件性,進而實現業務模組兒的獨立和解耦,增加可維護性和可延伸性。外掛使得第三方開發人員可以為系統做增值和拓展工作,也可以使其他開發人員協同開發相互配合,增加新的功能而不破壞現有的核心功能。外掛化還能夠促進將關注點分開,保證隱藏實現細節,且可以將測試獨立開來,並最具有實踐意義。
比如強大的Eclipse的平臺實際上就是一個所有功能都由外掛提供的骨架。Eclipse IDE自身(包括UI和Java開發環境)僅僅是一系列掛在核心框架上的外掛。
NDD的外掛化實現,是一種很好的範例,讓我們看到外掛化機制的好處,可以靈活的對軟體進行功能拓展,以下對NDD的外掛化實現原理做下分析。
用C++實現外掛機制的基本思路是:
一、應用程式(框架)提供出外掛介面。
二、由使用者或第三方實現這些介面,並編譯出相應的動態庫(即外掛);
三、將所有外掛放到某個特定目錄,應用程式(框架)執行時會自動搜尋該目錄,並動態載入目錄中的外掛。
按照以上思路,分析下NDD原始碼中的外掛機制實現。
NDD原始碼中提供出來的外掛介面有兩個,介面宣告如下:
#define NDD_EXPORT __declspec(dllexport) #ifdef __cplusplus extern "C" { #endif NDD_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,給出二級根選單的地址。其他值nullptr ndd_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應用程式啟動到外掛載入。過程大致如下:
int main(int argc, char *argv[]) { //可以防止某些螢幕下的字型擁擠重疊問題 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #ifdef Q_OS_MAC MyApplication a(argc, argv); #else QApplication 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; } //建立action if (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_PLUGIN PluginMgr* pWin = new PluginMgr(this, m_pluginList); pWin->setAttribute(Qt::WA_DeleteOnClose); pWin->show(); #else QMessageBox::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"; #else strFilter << "lib*.so"; #endif list = 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原始碼的外掛機制實現解析的文章就介紹到這了,更多相關c++ NDD原始碼外掛機制內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45