Análisis del mecanismo de complemento del código fuente de NDD del complemento c ++

El mecanismo de complemento es un marco que permite a los desarrolladores simplemente agregar o ampliar la funcionalidad dentro de una aplicación. Es muy utilizado porque se puede reutilizar como módulos y los hace más fáciles de mantener y ampliar, por lo que son muy útiles en las aplicaciones. El mecanismo de complemento permite a los administradores instalar y desinstalar complementos fácilmente cuando sea necesario sin realizar cambios en la aplicación subyacente.

Introducción a NDD

Aquí presentaré y recomendaré el excelente proyecto de código abierto de software doméstico NDD (notepad--). Un editor de texto compatible con Windows/Linux/Mac, el objetivo es reemplazar un software similar en China. En comparación con otro software de Bloc de notas de la competencia, la ventaja es que puede ser multiplataforma y es compatible con el sistema operativo Linux Mac. Esperamos que los chinos participen en código abierto y contribuyan con complementos más interesantes.

dirección del almacén de gitee: https://gitee.com/cxasm/notepad--

 Ventajas de los complementos

En función de la extensibilidad de los complementos, se puede realizar la independencia y el desacoplamiento de los módulos comerciales, y se puede aumentar la capacidad de mantenimiento y la escalabilidad. Los complementos permiten a los desarrolladores externos agregar valor y expandir el sistema, y ​​también permiten que otros desarrolladores desarrollen y cooperen entre sí para agregar nuevas funciones sin destruir las funciones principales existentes. El complemento también puede promover la separación de preocupaciones, garantizar que los detalles de implementación estén ocultos y puede separar las pruebas, que es lo más práctico.

Por ejemplo, la poderosa plataforma Eclipse es en realidad un esqueleto con todas las funciones provistas por complementos. Eclipse IDE en sí mismo (que incluye la interfaz de usuario y el entorno de desarrollo de Java) es solo una serie de complementos que cuelgan del marco central.

La implementación del complemento de NDD es un buen ejemplo. Veamos los beneficios del mecanismo del complemento, que puede expandir de manera flexible las funciones del software. El siguiente es un análisis del principio de implementación del complemento de NDD.

Análisis del mecanismo de complemento NDD

La idea básica de usar C++ para implementar el mecanismo de complemento es:

1. El programa de aplicación (marco) proporciona una interfaz de complemento.

2. Realizar estas interfaces por parte de los usuarios o terceros, y compilar las bibliotecas dinámicas correspondientes (es decir, complementos);

3. Coloque todos los complementos en un directorio específico y la aplicación (marco) buscará automáticamente en el directorio cuando se ejecute y cargará dinámicamente los complementos en el directorio.

De acuerdo con las ideas anteriores, analice la implementación del mecanismo de complemento en el código fuente de NDD.

interfaz de complemento

Hay dos interfaces de complemento proporcionadas en el código fuente de NDD, y las declaraciones de interfaz son las siguientes:

#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

Cabe señalar que la interfaz del complemento debe incluirse con "C" externo, porque el compilador de C++ decorará los símbolos en el programa.Este proceso se denomina decoración de símbolos (Name Decoration) o adaptación de símbolos (Name Mangling) en el compilador. Si no se cambia al método c, entonces el método de resolución de la biblioteca dinámica para encontrar la entrada no encontrará la entrada del identificador.

Las dos interfaces anteriores, una es la información de descripción relevante del complemento y la otra es la realización de la función principal del complemento.

implementación del complemento

La interfaz NDD_PROC_IDENTIFY es la más simple y se utiliza para que los desarrolladores de complementos completen la información del complemento. Los parámetros pasados ​​tienen la siguiente información:

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;
}
La otra interfaz es NDD_PROC_MAIN 
, que es la interfaz de implementación específica de la función del complemento. Los desarrolladores de complementos pueden implementar las funciones principales del complemento en esta interfaz.
//插件的入口点接口实现
//则点击菜单栏按钮时,会自动调用到该插件的入口点函数接口。
//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;
}

Después de completar las dos interfaces anteriores y compilarlas en una biblioteca dll dinámica, el desarrollo del complemento se completa. Si el compilador y la biblioteca QT utilizados son los mismos que los de la distribución NDD, simplemente coloque la biblioteca dll directamente en el directorio de complementos. A continuación, veamos cómo la aplicación NDD carga y usa complementos.

Proceso de carga del complemento NDD

Desde el lanzamiento de la aplicación ndd hasta la carga del complemento. El proceso es más o menos como sigue:

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
}

El proceso de carga del complemento se encuentra en la función loadPluginLib(), ingrese al directorio del complemento para cargar el complemento.

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

		loadPluginProcs(strDir,ui.menuPlugin);
	}
}

interfaz de la función de devolución de llamada foundCallback, después de encontrar la información del complemento, procéselo en la función onPlugFound para completar el enlace con el menú de la interfaz.

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

Active la ejecución de onPlugWork después de hacer clic en el menú. Si el menú secundario está habilitado, inicialice el menú secundario.

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

Aunque el proceso anterior parece un poco complicado, de hecho, la llamada clave es obtener el puntero de función y luego realizar algún procesamiento según sea necesario. La información del complemento se almacena en QList<NDD_PROC_DATA> m_pluginList. Hay una interfaz para mostrar esta información.

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
}

Para evitar caracteres ilegibles en chino, la forma de admitir chino es guardar la codificación del archivo en formato utf-8. Ingrese los caracteres chinos como se indica arriba, u8 "Caracteres chinos". El script de compilación se especifica de la siguiente manera:

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

La clave del mecanismo del complemento es definir punteros de función, obtener punteros de función y usar punteros de función. 

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

Supongo que te gusta

Origin blog.csdn.net/qq8864/article/details/129611495
Recomendado
Clasificación