Analysis of the plug-in mechanism of c++ plug-in NDD source code

The plugin mechanism is a framework that allows developers to simply add or extend functionality within an application. It is widely used because it can be reused as modules and makes them easier to maintain and extend, so they are very useful in applications. The plugin mechanism allows administrators to easily install and uninstall plugins when needed without making changes to the underlying application.

Introduction to NDD

Here I will introduce and recommend the excellent domestic software open source project NDD (notepad--). A text editor that supports windows/linux/mac, the goal is to replace similar software in China. Compared with other competing Notepad software, the advantage is that it can be cross-platform and supports the linux mac operating system. We look forward to Chinese people participating in open source and contributing more interesting plug-ins.

gitee warehouse address: https://gitee.com/cxasm/notepad--

 Advantages of plugins

Based on the extensibility of plug-ins, the independence and decoupling of business modules can be realized, and the maintainability and scalability can be increased. Plug-ins enable third-party developers to add value and expand the system, and also enable other developers to develop and cooperate with each other to add new functions without destroying existing core functions. Plug-in can also promote the separation of concerns, ensure that implementation details are hidden, and can separate tests, which is the most practical.

For example, the powerful Eclipse platform is actually a skeleton with all functions provided by plug-ins. Eclipse IDE itself (including UI and Java development environment) is just a series of plug-ins hanging on the core framework.

The plug-in implementation of NDD is a good example. Let us see the benefits of the plug-in mechanism, which can flexibly expand the functions of the software. The following is an analysis of the plug-in implementation principle of NDD.

NDD plug-in mechanism analysis

The basic idea of ​​​​using C++ to implement the plug-in mechanism is:

1. The application program (framework) provides a plug-in interface.

2. Realize these interfaces by users or third parties, and compile corresponding dynamic libraries (ie plug-ins);

3. Put all the plug-ins in a specific directory, and the application (framework) will automatically search the directory when it runs, and dynamically load the plug-ins in the directory.

According to the above ideas, analyze the implementation of the plug-in mechanism in the NDD source code.

plug-in interface

There are two plug-in interfaces provided in the NDD source code, and the interface declarations are as follows:

#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

It should be noted that the plug-in interface must be included with extern "C", because the C++ compiler will decorate the symbols in the program. This process is called symbol decoration (Name Decoration) or symbol adaptation (Name Mangling) in the compiler. If it is not changed to the c method, then the dynamic library resolve method of finding the entry will not find the handle entry.

The above two interfaces, one is the relevant description information of the plug-in, and the other is the realization of the core function of the plug-in.

plugin implementation

The NDD_PROC_IDENTIFY interface is the simplest and is used for plug-in developers to fill in plug-in information. The parameters passed in have the following information:

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;
}
Another interface is NDD_PROC_MAIN
This is the specific implementation interface of the plug-in function, and plug-in developers can implement the main functions of the plug-in in this interface.
//插件的入口点接口实现
//则点击菜单栏按钮时,会自动调用到该插件的入口点函数接口。
//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;
}

After completing the above two interfaces and compiling them into a dynamic dll library, the plug-in development is actually completed. If the compiler and the QT library used are the same as the NDD distribution, just put the dll library directly into the plugin directory. Next, let's look at how the NDD application loads and uses plugins.

NDD plugin loading process

From ndd application launch to plugin loading. The process is roughly as follows:

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
}

The loading process of the plug-in is in the loadPluginLib() function, enter the plugin directory to load the plug-in.

#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 callback function interface, after finding the plug-in information, process it in the onPlugFound function to complete the binding with the interface menu.

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));
	}
}

Trigger the execution of onPlugWork after clicking the menu. If the secondary menu is enabled, initialize the secondary menu.

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);
		}
	
	}
}

Although the above process seems a bit complicated, in fact the key call is to get the function pointer, and then do some processing as needed. Plugin information is stored in QList<NDD_PROC_DATA> m_pluginList. There is an interface to display this information.

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
}

In order to prevent Chinese garbled characters, the way to support Chinese is to save the file encoding as utf-8 format. Input Chinese characters as above, u8 "Chinese characters". The compile script is specified as follows:

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

The key to the plugin mechanism is to define function pointers, get function pointers, and use function pointers. 

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;
}

Guess you like

Origin blog.csdn.net/qq8864/article/details/129611495