您好,登錄后才能下訂單哦!
本篇內容主要講解“C++ NDD源碼的插件機制怎么實現”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“C++ NDD源碼的插件機制怎么實現”吧!
插件機制是一種框架,允許開發人員簡單地在應用程序中添加或擴展功能。它使廣泛使用,因為它可以作為模塊被重復使用,并使它們更易于維護和擴展,因此它們在應用程序中非常有用。插件機制允許管理員在需要時輕松安裝和卸載插件,而無需對基礎應用程序做出更改。
這里再介紹推薦下優秀的國產軟件開源項目 NDD(notepad--)。一個支持windows/linux/mac的文本編輯器,目標是要國產替換同類軟件。對比其它競品Notepad類軟件而言,優勢是可以跨平臺,支持linux mac操作系統。期待國人參與開源,貢獻更多有意思的插件。
基于插件的擴展性,進而實現業務模塊兒的獨立和解耦,增加可維護性和可擴展性。插件使得第三方開發人員可以為系統做增值和拓展工作,也可以使其他開發人員協同開發相互配合,增加新的功能而不破壞現有的核心功能。插件化還能夠促進將關注點分開,保證隱藏實現細節,且可以將測試獨立開來,并最具有實踐意義。
比如強大的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源碼的插件機制怎么實現”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。