关于程序插件机制的困惑

大板牙花生 2020-04-08 10:58:49
许多软件的构成是插件机制比如eclipse像这样的软件,动态加载插件也能够理解,反射机制也能够理解,只是不明白这些插件是如何能够在运行时候得到识别反馈的,比如加载插件后如何跟原有的功能进行通信?是通过回调机制吗?消息机制,有没有代码演示一下呢?
...全文
364 3 打赏 收藏 转发到动态 举报
写回复
用AI写文章
3 条回复
切换为时间正序
请发表友善的回复…
发表回复
大板牙花生 2020-04-26
  • 打赏
  • 举报
回复
还有一个疑问,如果UI也以插件的形式设计,在某个功能窗体上实现新的子功能窗体,这样是不是需要提前确定了功能窗体的接口规范了,如果子功能窗体不知道的目标功能窗体,该如何去设计这样的规范?
大板牙花生 2020-04-10
  • 打赏
  • 举报
回复
引用 1 楼 _mervyn 的回复:
每个支持所谓“插件”功能的程序,具体实现肯定都不会是一样的,这个没有统一标准。自己想怎么实现就怎么实现。
比如你自己想实现一套插件机制可以这样设计:
1、规定插件以怎样的形式存在,dll or exe?
2、规定插件必须实现一系列事先就定义好的接口,你不实现,我就不理你
3、这一系列接口你自己可以随意定义,这就很灵活了,你想要插件能做到什么程度,就定义到什么程度,只要最后给出文档,让插件开发者可以按文档开发就行。当然你的接口设计的好不好那就另说了。
4、定义插件安装机制,简单点的话,就一股脑放到你主程序的某个目录里,程序启动的时候扫描所有,把所有符合自己标准的(实现了我规定的所有接口)插件加载进来就好。当然,你也可以让用户自己在界面上选择需要加载哪些插件或者在程序运行时动态加载,如何识别还是一个道理。

关键的就是你如何定义接口了。以dll形式的插件为例,举个简单的例子:
插件初始化:

/** @fn typedef int (MODULE_API *Module_Init)()
* @brief 初始化模块;
* @param szPath [in] 模块目录;
* @return 成功返回0,否则返回其他值;
*/
typedef int (MODULE_API *Module_Init)(const char* szPath);


插件反初始化:
/** @fn typedef int (MODULE_API *Module_Uninit)()
* @brief 反初始化模块;
* @param void
* @return 成功返回0,否则返回其他值;
*/
typedef int (MODULE_API *Module_Uninit)();


其他需要实现的接口可以统一由一个Module_Proc定义.
功能接口Module_Proc的定义:

/** @brief 功能接口调用完成通知;
* @param error [in] 错误码;
* @param data [in] 数据结果;
* @param size [in] 数据长度;
* @param usr [in] 用户数据;
* @return 成功返回0,失败返回其他值;
*/
typedef int (SRPC_API *COMPLETIONPROC)(unsigned int error,const void* data,unsigned int size,void* usr);

//************************************
// Method: Module_Proc
// Brief: 功能过程;
// Returns: int
// Parameter: void* data_in 数据(参数);
// Parameter: unsigned int size_in 数据大小;
// Parameter: COMPLETIONPROC proc 完成回调;
// Parameter: void* usr 框架内部数据,需要用户传给proc;
//note:
// 注意:一般情况下,proc 和 usr 只能在该函数之内使用以同步返回结果!;除非用户返回100;
// 这意味着,如果该函数返回了100,框架内部结构usr的生存权将转移给用户,其不会被释放直到用户调用了proc;
//************************************
typedef int (MODULE_API *Module_Proc)( const void* data_in, unsigned int size_in, COMPLETIONPROC proc,void* usr);


然后,插件库内部就可以定义 Module_Proc 形式的功能函数地址了。功能可以有很多个,一般来讲,我们需要插件提供的功能都是接收我们主程序的通知,比如:程序启动通知,程序退出通知,用户干了啥啥啥通知,等等等等。我们可以只定义一个接口,让插件实现,以便我们一次性去获取插件提供的所有功能,那么可以这样定义一个Module_GetAllExportProc接口:

/** @struct _PROCINFO
* @brief 过程信息表;用key表示功能的名称字符串,pAddr为功能函数的地址
* 比如key可以是“onProgamStartEnd”表示我们的主程序启动完毕了,如果插件在这个时候想做什么,就去实现这个接口。
* 比如key可以是“onReportStatus”表示我们的主程序想要获取插件的运行状态,插件如果想报告运行状态,就去实现这个接口。
*/
typedef struct _PROCINFO
{
char szKey[MODULE_KEY_MAXLENTH];
Module_Proc pAddr;
}PROCINFO;

/** @fn typedef int (MODULE_API *Module_RegisterProc)(const PROCINFO* procedures,unsigned int count,void* usr);
* @brief 注册过程信息;
* @param const PROCINFO* [in] 过程信息表;
* @param unsigned int [in] 注册数量;
* @param void* [in] 用户数据;
* @return 成功返回0,否则返回其他值;
*/
typedef int (MODULE_API *Module_RegisterProc)(const PROCINFO* procedures,unsigned int count,void* usr);

/** @fn typedef int (MODULE_API *Module_GetAllExportProc)(Module_FnInfo fnReg);
* @brief 获取所有导出的过程地址;
* @param Module_RegisterProc [in] 过程注册地址;
* @param void* [in] 用户数据;
* @return 成功返回0,否则返回其他值;
*/
typedef int (MODULE_API *Module_GetAllExportProc)(Module_RegisterProc regAddr,void* usr);

Module_GetAllExportProc 接收两个参数,都是由我们的主程序在加载后,来调用传入的,一个参数是Module_RegisterProc函数地址,使插件开发者可以调用它来导出自己所有的功能函数,另一个参数usr是Module_RegisterProc所需要的参数。

那么以插件实现者的角度,简单给一个实现Module_GetAllExportProc形式的例子:
假设,我们的插件有两个功能要实现,分别是onProgamStartEnd接收程序启动完毕通知、onReportStatus报告插件运行状态:

int MODULE_API Plugin_OnProgamStartEnd(const void* data_in, unsigned int size_in, COMPLETIONPROC fn,void* usr)
{
//这里就可以做一些程序启动完毕时要做的处理
//...
return 0;
}


int MODULE_API Plugin_OnReportStatus( const void* data_in, unsigned int size_in, COMPLETIONPROC fn,void* usr)
{
//使用fn向我们主程序报告运行状态
if (fn)
{
fn(0,"这里可以是任何内容表示当前的运行状态,比如用json", 内容长度, usr);
}
return 0;
}


导出这两个功能地址,以便主程序调用

int MODULE_API GetAllExportProcName(Module_RegisterProc regAddr,void* usr)
{
if(regAddr)
{
const PROCINFO proc_info_t[] =
{
{"onProgamStartEnd", &Plugin_OnProgamStartEnd},
{"onReportStatus", &Plugin_OnReportStatus},
{NULL,NULL}
};
unsigned int count = sizeof(proc_info_t) / sizeof(PROCINFO);
return regAddr(proc_info_t,count,usr);
}
return -1;
}


到目前为止,我们定义了1、插件的初始化接口,2、插件的反初始化接口,3、插件的所有功能获取接口。同时给了接口3的实现例子。
插件确实可以被动接收我们主程序的通知或者调用了,那么插件如何主动调用主程序的功能呢?
那其实也可以通过类似Module_GetAllExportProc的定义方式,来获取主程序的所有功能,只不过这一次,Module_GetAllExportProc实现在我们的主程序中,并将它的地址通过某种方式告诉插件,让插件去获取主程序的所有功能地址。
这个“某种方式”可以是:
a、在Module_Init中新加一个参数接收
b、新定义的一个接口要求插件实现
c、直接让插件在上面例子的GetAllExportProcName函数中导出一个叫onProgamAllProcs的功能来接收主程序的所有功能名及功能函数地址。

这里我们先以最简单的a方法重新定义一下初始化函数:

/** @fn typedef int (MODULE_API *Module_Init)()
* @brief 初始化模块;
* @param szPath [in] 模块目录;
* @param getAllProgamProcs [in] 主程序所有功能获取接口;
* @return 成功返回0,否则返回其他值;
*/
typedef int (MODULE_API *Module_Init)(const char* szPath , Module_GetAllExportProc getAllProgamProcs);

实现之:
int MODULE_API Plugin_Init(const char* szPath, Module_GetAllExportProc getAllProgamProcs)
{
// do something Init first.....
//...

//获取主程序的所有功能
if(getAllProgamProcs)
{
getAllProgamProcs(®isterProcCallback, NULL);
}
return 0;
}

int MODULE_API RegisterProcCallback(const PROCINFO* procedures,unsigned int count,void* usr)
{
for (unsigned int i = 0; i < count; i++)
{
if (procedures[i].pAddr)
{
//处理,比如将主程序的所有功能及对应地址保存起来,供后续调用...
//addFnInterface(procedures[i].szKey, procedures[i].pAddr);
//...
//比如主程序有个功能是打开一个文件,那我们把这个功能名叫做 ProgamOpenFile
//这里就会有一个procedures[i].szKey 等于"ProgamOpenFile",其功能函数地址就是procedures[i].pAddr
//当插件想要使用这个功能就调用它就完事了: procedures[i].pAddr(...);
//当然主程序有哪些功能,叫什么名字,接受的参数格式什么样的,那就要我们写好的文档供插件开发者查阅了。
}
}
return 0;
}


如果用b方法,重新定义一个接口要求插件实现,也是一样的,就不演示了。

如果用c方法也是可以的,也是多实现一个插件功能用来接收主程序的所有功能通知。

int MODULE_API Plugin_OnProgamAllProcs(const void* data_in, unsigned int size_in, COMPLETIONPROC fn,void* usr)
{
//这里data_in内容其实可以任意定义,如果是dll插件,简单点可以直接将内容定义成Module_GetAllExportProc函数指针,那么size_in就是指针长度

// Module_GetAllExportProc* pGetAllProgamProcs = (Module_GetAllExportProc*)data_in;
// 等同于 memcpy(pGetAllProgamProcs, data_in, sizeof(Module_GetAllExportProc));

// 之后就和上面Plugin_Init例子里一样了:
// if(*pGetAllProgamProcs)
// {
// (*pGetAllProgamProcs)(®isterProcCallback, NULL);
// }
//...

//或者为了更通用,以适用于exe插件,data_in可以是json,包含所有主程序的功能名。这里就不作展开了。
return 0;
}
//修改GetAllExportProcName 增加onProgamAllProcs通知接收
int MODULE_API GetAllExportProcName(Module_RegisterProc regAddr,void* usr)
{
if(regAddr)
{
const PROCINFO proc_info_t[] =
{
{"onProgamAllProcs", &Plugin_OnProgamAllProcs},
{"onProgamStartEnd", &Plugin_OnProgamStartEnd},
{"onReportStatus", &Plugin_OnReportStatus},
{NULL,NULL}
};
unsigned int count = sizeof(proc_info_t) / sizeof(PROCINFO);
return regAddr(proc_info_t,count,usr);
}
return -1;
}


另外,我们还可以把定义的3个接口(Module_Init,Module_Uninit,Module_GetAllExportProc)合并成一个Module_GetMEAT,这样我们加载插件后,只要检查这一个接口有没有实现就可以判断它是否为合法插件了。具体方法原理还是一样的,可以参考Module_GetAllExportProc

//----------- 模块库必须导出的接口; ------------//
/*
*
* 模块内部必须实现 _MODULE_EXPORT_ADDR_T 定义的一系列接口;
* 框架通过这些接口与模块进行交互;
*
* 模块库必须导出下面声明的接口,框架通过该接口识别动态库是否为合法模块;
* MODULE_EXTERN int MODULE_API MODULE_GetMEAT(Module_ExportAddr eFunc,void* usr)
*
*
*/

/** @struct _MODULE_EXPORT_ADDR_T
* @brief 模块与框架交互的接口地址表;
*/
typedef struct _MODULE_EXPORT_ADDR_T
{
Module_Init Init; ///< 模块初始化接口地址;
Module_Uninit Uninit; ///< 模块反初始化接口地址;
Module_GetAllExportProc GetAllProc; ///< 获取所有暴露过程接口地址;
}MEAT;

/** @fn typedef int (MODULE_API *Module_ExportAddr)(MEAT* pMEAT);
* @brief 导出接口地址表;
* @param MEAT* [in] 地址表;
* @param void* [in] 用户数据;
* @return 成功返回0,否则返回其他值;
*/
typedef int (MODULE_API *Module_ExportAddr)(const MEAT* pMEAT,void* usr);

/** @fn typedef int (MODULE_API *Module_GetMEAT)();
* @brief 获取模块与框架交互的接口地址表;
* @param Module_ExportAddr [in] 导出接口地址表的函数地址;
* @param void* [in] 用户数据;
* @return 成功返回0,否则返回其他值;
*/
typedef int (*Module_GetMEAT)(Module_ExportAddr eFunc,void* usr);


这样,插件只要实现这个接口,并导出这唯一的一个接口就可以了:

MODULE_EXTERN int Plugin_GetMEAT(Module_ExportAddr eFunc,void* usr)
{
if(eFunc)
{
const MEAT export_addr_t = {
Plugin_Init,
Plugin_Uninit,
GetAllExportProcName
};
return eFunc(&export_addr_t,usr);
}
return -1;
}

相关宏:

#if (defined(_WIN32) || defined(_WIN64))
# define MODULE_EXTERN extern "C" __declspec(dllexport)
# define MODULE_API __stdcall
#elif defined(__linux__)
# define MODULE_EXTERN extern "C"
# define MODULE_API
#else
# define MODULE_EXTERN
# define MODULE_API
#endif


以上,其实就是本人以前设计的一个模块化开发方案的一部分。不光支持dll、so形式的动态库模块,也可以是exe这样的可执行模块。不仅适用于是同进程内的通信,也适用于不同进程甚至不同主机间的通信。这也解释了为什么我将“功能接口”形式统一成了这样的固定形式,又将它与一个字符串名字关联起来。就是为了方便不同进程间的通信。
那么将它用在“插件开发”上其实也是绰绰有余的。

总之,插件机制的实现可以有很多种,我举的这个例子只是其中一种,至于它怎么样,就见仁见智了。仅供参考。
对大师的膜拜,是我等凡人的荣幸。这里面体现了很多思维是我这些渣渣所学习不到的。请教一下大神,除了大量的代码经验积累之外,有没有权威的代码库或者是书籍推荐一下,为写这些良好架构设计的软件提供一些思路指引?
_mervyn 2020-04-09
  • 打赏
  • 举报
回复
每个支持所谓“插件”功能的程序,具体实现肯定都不会是一样的,这个没有统一标准。自己想怎么实现就怎么实现。 比如你自己想实现一套插件机制可以这样设计: 1、规定插件以怎样的形式存在,dll or exe? 2、规定插件必须实现一系列事先就定义好的接口,你不实现,我就不理你 3、这一系列接口你自己可以随意定义,这就很灵活了,你想要插件能做到什么程度,就定义到什么程度,只要最后给出文档,让插件开发者可以按文档开发就行。当然你的接口设计的好不好那就另说了。 4、定义插件安装机制,简单点的话,就一股脑放到你主程序的某个目录里,程序启动的时候扫描所有,把所有符合自己标准的(实现了我规定的所有接口)插件加载进来就好。当然,你也可以让用户自己在界面上选择需要加载哪些插件或者在程序运行时动态加载,如何识别还是一个道理。 关键的就是你如何定义接口了。以dll形式的插件为例,举个简单的例子: 插件初始化:

/** @fn typedef int (MODULE_API *Module_Init)()
 *  @brief 初始化模块;
 *  @param szPath [in] 模块目录;
 *  @return 成功返回0,否则返回其他值;
 */
typedef int (MODULE_API *Module_Init)(const char* szPath);
插件反初始化:
/** @fn typedef int (MODULE_API *Module_Uninit)()
 *  @brief 反初始化模块;
 *  @param void
 *  @return 成功返回0,否则返回其他值;
 */
typedef int (MODULE_API *Module_Uninit)();
其他需要实现的接口可以统一由一个Module_Proc定义. 功能接口Module_Proc的定义:

/** @brief 功能接口调用完成通知;
 *  @param error [in] 错误码;
 *  @param data [in] 数据结果;
 *  @param size [in]  数据长度;
 *  @param usr [in]  用户数据;
 *  @return 成功返回0,失败返回其他值;
 */
typedef int (SRPC_API *COMPLETIONPROC)(unsigned int error,const void* data,unsigned int size,void* usr);

//************************************
// Method:    Module_Proc
// Brief:  	  功能过程;
// Returns:   int		
// Parameter: void* data_in				数据(参数);
// Parameter: unsigned int size_in		数据大小;
// Parameter: COMPLETIONPROC proc		完成回调;
// Parameter: void* usr					框架内部数据,需要用户传给proc;
//note:
// 注意:一般情况下,proc 和 usr 只能在该函数之内使用以同步返回结果!;除非用户返回100;
// 这意味着,如果该函数返回了100,框架内部结构usr的生存权将转移给用户,其不会被释放直到用户调用了proc;
//************************************
typedef int (MODULE_API *Module_Proc)( const void* data_in, unsigned int size_in, COMPLETIONPROC proc,void* usr);
然后,插件库内部就可以定义 Module_Proc 形式的功能函数地址了。功能可以有很多个,一般来讲,我们需要插件提供的功能都是接收我们主程序的通知,比如:程序启动通知,程序退出通知,用户干了啥啥啥通知,等等等等。我们可以只定义一个接口,让插件实现,以便我们一次性去获取插件提供的所有功能,那么可以这样定义一个Module_GetAllExportProc接口:

/** @struct _PROCINFO
 *  @brief 过程信息表;用key表示功能的名称字符串,pAddr为功能函数的地址
 * 比如key可以是“onProgamStartEnd”表示我们的主程序启动完毕了,如果插件在这个时候想做什么,就去实现这个接口。
 * 比如key可以是“onReportStatus”表示我们的主程序想要获取插件的运行状态,插件如果想报告运行状态,就去实现这个接口。
 */
typedef struct _PROCINFO
{
	char			szKey[MODULE_KEY_MAXLENTH];
	Module_Proc		pAddr;
}PROCINFO;

/** @fn typedef int (MODULE_API *Module_RegisterProc)(const PROCINFO* procedures,unsigned int count,void* usr);
 *  @brief 注册过程信息;
 *  @param const PROCINFO* [in]		过程信息表;
 *  @param unsigned int [in]		注册数量;
 *  @param void* [in]				用户数据;
 *  @return 成功返回0,否则返回其他值;
 */
typedef int (MODULE_API *Module_RegisterProc)(const PROCINFO* procedures,unsigned int count,void* usr);

/** @fn typedef int (MODULE_API *Module_GetAllExportProc)(Module_FnInfo fnReg);
 *  @brief 获取所有导出的过程地址;
 *  @param Module_RegisterProc [in] 过程注册地址;
 *  @param void* [in]				用户数据;
 *  @return 成功返回0,否则返回其他值;
 */
typedef int (MODULE_API *Module_GetAllExportProc)(Module_RegisterProc regAddr,void* usr);
Module_GetAllExportProc 接收两个参数,都是由我们的主程序在加载后,来调用传入的,一个参数是Module_RegisterProc函数地址,使插件开发者可以调用它来导出自己所有的功能函数,另一个参数usr是Module_RegisterProc所需要的参数。 那么以插件实现者的角度,简单给一个实现Module_GetAllExportProc形式的例子: 假设,我们的插件有两个功能要实现,分别是onProgamStartEnd接收程序启动完毕通知、onReportStatus报告插件运行状态:

int MODULE_API Plugin_OnProgamStartEnd(const void* data_in, unsigned int size_in, COMPLETIONPROC fn,void* usr)
{
	//这里就可以做一些程序启动完毕时要做的处理
	//...
	return 0;
}

int MODULE_API Plugin_OnReportStatus( const void* data_in, unsigned int size_in, COMPLETIONPROC fn,void* usr)
{      
	//使用fn向我们主程序报告运行状态
	if (fn)
	{
		fn(0,"这里可以是任何内容表示当前的运行状态,比如用json",  内容长度,  usr);
	}
	return 0;
}
导出这两个功能地址,以便主程序调用

int MODULE_API GetAllExportProcName(Module_RegisterProc regAddr,void* usr) 
{ 
	if(regAddr) 
	{
		const PROCINFO proc_info_t[] = 
		{
			{"onProgamStartEnd", &Plugin_OnProgamStartEnd},
			{"onReportStatus", &Plugin_OnReportStatus},
			{NULL,NULL} 
		};
		unsigned int count = sizeof(proc_info_t) / sizeof(PROCINFO);
		return regAddr(proc_info_t,count,usr);
	}
	return -1;
}
到目前为止,我们定义了1、插件的初始化接口,2、插件的反初始化接口,3、插件的所有功能获取接口。同时给了接口3的实现例子。 插件确实可以被动接收我们主程序的通知或者调用了,那么插件如何主动调用主程序的功能呢? 那其实也可以通过类似Module_GetAllExportProc的定义方式,来获取主程序的所有功能,只不过这一次,Module_GetAllExportProc实现在我们的主程序中,并将它的地址通过某种方式告诉插件,让插件去获取主程序的所有功能地址。 这个“某种方式”可以是: a、在Module_Init中新加一个参数接收 b、新定义的一个接口要求插件实现 c、直接让插件在上面例子的GetAllExportProcName函数中导出一个叫onProgamAllProcs的功能来接收主程序的所有功能名及功能函数地址。 这里我们先以最简单的a方法重新定义一下初始化函数:

/** @fn typedef int (MODULE_API *Module_Init)()
 *  @brief 初始化模块;
 *  @param szPath [in] 模块目录;
 *  @param getAllProgamProcs [in] 主程序所有功能获取接口;
 *  @return 成功返回0,否则返回其他值;
 */
typedef int (MODULE_API *Module_Init)(const char* szPath , Module_GetAllExportProc getAllProgamProcs);
实现之:
int MODULE_API Plugin_Init(const char* szPath, Module_GetAllExportProc getAllProgamProcs)
{
	// do something Init first.....
	//...
	
	//获取主程序的所有功能
	if(getAllProgamProcs)
	{
		getAllProgamProcs(®isterProcCallback, NULL);
	}
	return 0;
}
int MODULE_API RegisterProcCallback(const PROCINFO* procedures,unsigned int count,void* usr)
{
	for (unsigned int i = 0; i < count; i++)
	{
		if (procedures[i].pAddr)
		{
			//处理,比如将主程序的所有功能及对应地址保存起来,供后续调用...
			//addFnInterface(procedures[i].szKey, procedures[i].pAddr);
			//...
			//比如主程序有个功能是打开一个文件,那我们把这个功能名叫做 ProgamOpenFile
			//这里就会有一个procedures[i].szKey 等于"ProgamOpenFile",其功能函数地址就是procedures[i].pAddr
			//当插件想要使用这个功能就调用它就完事了: procedures[i].pAddr(...);
			//当然主程序有哪些功能,叫什么名字,接受的参数格式什么样的,那就要我们写好的文档供插件开发者查阅了。
		}			
	}
	return 0;
}
如果用b方法,重新定义一个接口要求插件实现,也是一样的,就不演示了。 如果用c方法也是可以的,也是多实现一个插件功能用来接收主程序的所有功能通知。

int MODULE_API Plugin_OnProgamAllProcs(const void* data_in, unsigned int size_in, COMPLETIONPROC fn,void* usr)
{
	//这里data_in内容其实可以任意定义,如果是dll插件,简单点可以直接将内容定义成Module_GetAllExportProc函数指针,那么size_in就是指针长度
	
	// Module_GetAllExportProc* pGetAllProgamProcs = (Module_GetAllExportProc*)data_in;
	// 等同于 memcpy(pGetAllProgamProcs, data_in, sizeof(Module_GetAllExportProc));
	
	// 之后就和上面Plugin_Init例子里一样了:
	// if(*pGetAllProgamProcs)
	// {
	//	 (*pGetAllProgamProcs)(®isterProcCallback, NULL);
	// }
	//...
	
	//或者为了更通用,以适用于exe插件,data_in可以是json,包含所有主程序的功能名。这里就不作展开了。
	return 0;
}
//修改GetAllExportProcName 增加onProgamAllProcs通知接收
int MODULE_API GetAllExportProcName(Module_RegisterProc regAddr,void* usr) 
{ 
	if(regAddr) 
	{
		const PROCINFO proc_info_t[] = 
		{
			{"onProgamAllProcs", &Plugin_OnProgamAllProcs},
			{"onProgamStartEnd", &Plugin_OnProgamStartEnd},
			{"onReportStatus", &Plugin_OnReportStatus},
			{NULL,NULL} 
		};
		unsigned int count = sizeof(proc_info_t) / sizeof(PROCINFO);
		return regAddr(proc_info_t,count,usr);
	}
	return -1;
}
另外,我们还可以把定义的3个接口(Module_Init,Module_Uninit,Module_GetAllExportProc)合并成一个Module_GetMEAT,这样我们加载插件后,只要检查这一个接口有没有实现就可以判断它是否为合法插件了。具体方法原理还是一样的,可以参考Module_GetAllExportProc

//----------- 模块库必须导出的接口; ------------//
/*
 *
 * 模块内部必须实现 _MODULE_EXPORT_ADDR_T 定义的一系列接口;
 * 框架通过这些接口与模块进行交互;
 *
 * 模块库必须导出下面声明的接口,框架通过该接口识别动态库是否为合法模块;
 * MODULE_EXTERN int MODULE_API MODULE_GetMEAT(Module_ExportAddr eFunc,void* usr)
 *
 *
 */

/** @struct _MODULE_EXPORT_ADDR_T
 *  @brief 模块与框架交互的接口地址表;
 */
typedef struct _MODULE_EXPORT_ADDR_T
{
    Module_Init					Init;			///< 模块初始化接口地址;
    Module_Uninit				Uninit;			///< 模块反初始化接口地址;
	Module_GetAllExportProc		GetAllProc;		///< 获取所有暴露过程接口地址;
}MEAT;

/** @fn typedef int (MODULE_API *Module_ExportAddr)(MEAT* pMEAT);
 *  @brief 导出接口地址表;
 *  @param MEAT* [in]  地址表;
 *  @param void* [in]  用户数据;
 *  @return 成功返回0,否则返回其他值;
 */
typedef int (MODULE_API *Module_ExportAddr)(const MEAT* pMEAT,void* usr);

/** @fn typedef int (MODULE_API *Module_GetMEAT)();
 *  @brief 获取模块与框架交互的接口地址表;
 *  @param Module_ExportAddr [in]  导出接口地址表的函数地址;
 *  @param void* [in]  用户数据;
 *  @return 成功返回0,否则返回其他值;
 */
typedef int (*Module_GetMEAT)(Module_ExportAddr eFunc,void* usr);
这样,插件只要实现这个接口,并导出这唯一的一个接口就可以了:

MODULE_EXTERN int Plugin_GetMEAT(Module_ExportAddr eFunc,void* usr)
{ 
	if(eFunc) 
	{
		const MEAT export_addr_t = { 
			Plugin_Init, 
			Plugin_Uninit, 
			GetAllExportProcName 
		}; 
		return eFunc(&export_addr_t,usr);
	}
    return -1; 
}
相关宏:

#if (defined(_WIN32) || defined(_WIN64))
#  define MODULE_EXTERN extern "C" __declspec(dllexport)
#  define MODULE_API __stdcall
#elif defined(__linux__)
#  define MODULE_EXTERN extern "C"
#  define MODULE_API
#else
#  define MODULE_EXTERN
#  define MODULE_API
#endif
以上,其实就是本人以前设计的一个模块化开发方案的一部分。不光支持dll、so形式的动态库模块,也可以是exe这样的可执行模块。不仅适用于是同进程内的通信,也适用于不同进程甚至不同主机间的通信。这也解释了为什么我将“功能接口”形式统一成了这样的固定形式,又将它与一个字符串名字关联起来。就是为了方便不同进程间的通信。 那么将它用在“插件开发”上其实也是绰绰有余的。 总之,插件机制的实现可以有很多种,我举的这个例子只是其中一种,至于它怎么样,就见仁见智了。仅供参考。
由国内著名技术社区联合推荐的2012年IT技术力作:《高性能Linux服务器构建实战:运维监控、性能调优与集群应用》,即将上架发行,此书从Web应用、数据备份与恢复、网络存储应用、运维监控与性能优化、集群高级应用等多个方面深入讲解了如何构建高性能的Linux服务器。其中蕴含了丰富的运维经验。更为重要的是,本书的内容不受硬件环境的限制,而且包含大量实用性极强的案例。对于广大Linux运维人员和系统管理人员来说,具有非常实用的指导意义。 全书共分五个篇幅,由14个章节组成,内容涉及Web应用、数据备份恢复、网络存储应用、性能优化与运维监控、集群高级应用方面,每个篇幅占用比例分别为:20%、20%、14%、14%,32%。 前言 第1篇 Web应用篇 第1章 轻量级HTTP服务器Nginx 1.1 什么是Nginx 1.2 为什么要选择Nginx 1.2.1 Nginx与Apache的异同 1.2.2 选择Nginx的优势所在 1.3 Nginx的模块与工作原理 1.4 Nginx的安装与配置 1.4.1 下载与安装Nginx 1.4.2 Nginx配置文件的结构 1.4.3 配置与调试Nginx 1.4.4 Nginx的启动、关闭和平滑重启 1.5 Nginx常用配置实例 1.5.1 虚拟主机配置实例 1.5.2 负载均衡配置实例 1.5.3 防盗链配置实例 1.5.4 日志分割配置实例 1.6 Nginx性能优化技巧 1.6.1 编译安装过程优化 1.6.2 利用TCMalloc优化Nginx的性能 1.6.3 Nginx内核参数优化 1.7 实战Nginx与PHP(FastCGI)的安装、配置与优化 1.7.1 什么是 FastCGI 1.7.2 Nginx+FastCGI运行原理 1.7.3 spawn-fcgi与PHP-FPM 1.7.4 PHP与PHP-FPM的安装及优化 1.7.5 配置Nginx来支持PHP 1.7.6 测试Nginx对PHP的解析功能 1.7.7 优化Nginx中FastCGI参数的实例 1.8 实战Nginx与Perl、Java的安装与配置 1.8.1 Perl(FastCGI)的安装 1.8.2 为Nginx添加FCGI支持 1.8.3 测试Nginx +Perl(FastCGI) 1.8.4 搭建Nginx+Java环境 1.9 本章小结 第2章 高性能HTTP加速器Varnish 2.1 初识Varnish 2.1.1 Varnish概述 2.1.2 Varnish的结构与特点 2.1.3 Varnish与Squid的对比 2.2 开始安装Varnish 2.2.1 安装前的准备 2.2.2 获取Varnish软件 2.2.3 安装pcre 2.2.4 安装Varnish 2.3 配置Varnish 2.3.1 VCL使用说明 2.3.2 配置一个简单的Varnish实例 2.3.3 Varnish对应多台Web服务器的配置实例 2.4 运行Varnish 2.4.1 varnishd指令 2.4.2 配置Varnish运行脚本 2.4.3 管理Varnish运行日志 2.5 管理Varnish 2.5.1 查看Varnish进程 2.5.2 查看Varnish缓存效果与状态 2.5.3 通过端口管理Varnish 2.5.4 管理Varnish缓存内容 2.6 Varnish优化 2.6.1 优化Linux内核参数 2.6.2 优化系统资源 2.6.3 优化Varnish参数 2.7 Varnish的常见应用实例 2.7.1 利用Varnish实现图片防盗链 2.7.2 利用Varnish实现静态文件压缩处理 2.8 本章小结 第3章 Memcached应用实战 3.1 Memcached基础 3.1.1 什么是Memcached 3.1.2 Memcached的特征 3.1.3 Memcached的安装 3.1.4 Memcached的简单使用过程 3.2 剖析Memcached的工作原理 3.2.1 Memcached的工作过程 3.2.2 Slab Allocation的工作机制 3.2.3 Memcached的删除机制 3.2.4 Memcached的分布式算法 3.3 Memcached的管理与性能监控 3.3.1 如何管理Memcached 3.3.2 Memcached的监控 3.3.3 Memcached变种产品介绍 3.4 通过UDFs实现Memcached与MySQL的自动更新 3.4.1 UDFs使用简介 3.4.2 memcached_functions_mysql应用实例 3.4.3 对memcached_functions_mysql的简单功能进行测试 3.4.4 使用memcached_functions_mysql的经验与技巧 3.5 本章小结 第2篇 数据备份恢复篇 第4章 开源网络备份软件bacula 4.1 bacula总体概述 4.1.1 bacula是什么 4.1.2 bacula适合哪些用户 4.1.3 bacula的功能特点 4.1.4 bacula的工作原理 4.2 安装bacula 4.2.1 bacula的几种网络备份拓扑 4.2.2 编译与安装bacula 4.2.3 初始化MySQL数据库 4.3 配置一个bacula备份系统 4.3.1 配置bacula的Console端 4.3.2 配置bacula的Director端 4.3.3 配置bacula的SD 4.3.4 配置bacula的FD端 4.4 启动与关闭bacula 4.4.1 启动bacula的Director daemon与Storage daemon 4.4.2 在客户端FD启动File daemon 4.5 实战bacula备份恢复过程 4.5.1 实例演示bacula的完全备份功能 4.5.2 实例演示bacula的增量备份功能 4.5.3 实例演示bacula的差异备份功能 4.5.4 实例演示bacula的完全恢复功能 4.5.5 实例演示bacula的不完全恢复功能 4.6 本章小结 第5章 数据镜像备份工具rsync与unison 5.1 rsync简介 5.1.1 什么是rsync 5.1.2 rsync的功能特性 5.1.3 下载与安装rsync软件 5.2 利用rsync搭建数据镜像备份系统 5.2.1 rsync的应用模式 5.2.2 企业案例:搭建远程容灾备份系统 5.3 通过rsync+inotify实现数据的实时备份 5.3.1 rsync的优点与不足 5.3.2 初识inotify 5.3.3 安装inotify工具inotify-tools 5.3.4 inotify相关参数 5.3.5 inotifywait相关参数 5.3.6 企业应用案例:利用rsync+inotify搭建实时同步系统 5.4 unison简介 5.5 安装unison 5.6 配置双机ssh信任 5.6.1 在两台机器上创建 RSA密钥 5.6.2 添加密钥到授权密钥文件中 5.7 unison的使用 5.7.1 本地使用unison 5.7.2 远程使用unison 5.7.3 unison参数说明 5.7.4 通过配置文件来使用unison 5.8 本章小结 第6章 ext3文件系统反删除利器ext3grep 6.1 “rm–rf”带来的困惑 6.2 ext3grep的安装与使用 6.2.1 ext3grep的恢复原理 6.2.2 ext3grep的安装过程 6.3 通过ext3grep恢复误删除的文件与目录 6.3.1 数据恢复准则 6.3.2 实战ext3grep恢复文件 6.4 通过ext3grep恢复误删除的MySQL表 6.4.1 MySQL存储引擎介绍 6.4.2 模拟MySQL表被误删除的环境 6.4.3 通过ext3grep分析数据、恢复数据 6.5 本章小结 第3篇 网络存储应用篇 第7章 IP网络存储iSCSI 7.1 存储的概念与术语 7.1.1 SCSI介绍 7.1.2 FC介绍 7.1.3 DAS介绍 7.1.4 NAS介绍 7.1.5 SAN介绍 7.2 iSCSI的概念 7.3 FC SAN与IP SAN 7.4 iSCSI的组成 7.4.1 iSCSI Initiator 7.4.2 iSCSI Target 7.5 iSCSI的工作原理 7.6 搭建基于IP SAN的iSCSI存储系统 7.6.1 安装iSCSI Target软件 7.6.2 配置一个简单的iSCSI Target 7.6.3 在Windows上配置iSCSI Initiator 7.6.4 在Linux上配置iSCSI Initiator 7.7 iSCSI 在安全方面的相关设定 7.7.1 Initiator主机以IP认证方式获取iSCSI Target资源 7.7.2 Initiator主机以密码认证方式获取iSCSI Target资源 7.8 iSCSI性能优化方案 7.8.1 iSCSI性能瓶颈 7.8.2 iSCSI性能优化 7.9 本章小结 第8章 分布式存储系统MFS 8.1 MFS概论 8.2 MFS 文件系统 8.2.1 MFS文件系统结构 8.2.2 MFS的编译与安装实例 8.3 编译与使用MFS的经验总结 8.3.1 安装选项说明 8.3.2 管理服务器 8.3.3 元数据日志服务器 8.3.4 数据存储服务器 8.3.5 客户端挂载 8.4 管理与使用MFS 8.4.1 在客户端挂载文件系统 8.4.2 MFS常用操作 8.4.3 为垃圾箱设定隔离时间 8.4.4 快照 8.4.5 MFS的其他命令 8.5 维护MFS 8.5.1 启动MFS集群 8.5.2 停止MFS集群 8.5.3 MFS 数据存储服务器的维护 8.5.4 MFS元数据的备份 8.5.5 MFS 管理服务器的恢复 8.5.6 从备份恢复MFS 管理服务器 8.6 通过冗余实现失败防护的解决方案 8.7 本章小结 第4篇 运维监控与性能优化篇 第9章 运维监控利器Nagios 9.1 Nagios综述 9.1.1 什么是Nagios 9.1.2 Nagios的结构与特点 9.2 Nagios的安装与配置 9.2.1 安装Nagios 9.2.2 配置Nagios 9.3 Nagios的运行和维护 9.3.1 验证Nagios配置文件的正确性 9.3.2 启动与停止Nagios 9.3.3 Nagios故障报警 9.4 Nagios性能分析图表的实现 9.4.1 Nagios性能分析图表的作用 9.4.2 PNP的概念与安装环境 9.4.3 安装PNP 9.4.4 配置PNP 9.4.5 修改Nagios配置文件 9.4.6 测试PNP功能 9.5 利用插件扩展Nagios的监控功能 9.5.1 利用NRPE外部构件监控远程主机 9.5.2 利用飞信实现Nagios短信报警功能 9.6 本章小结 第10章 基于Linux服务器的性能分析与优化 10.1 系统性能分析的目的 10.1.1 找到系统性能的瓶颈 10.1.2 提供性能优化方案 10.1.3 使系统硬件和软件资源的使用达到平衡 10.2 分析系统性能涉及的人员 10.2.1 Linux系统管理人员 10.2.2 系统架构设计人员 10.2.3 软件开发人员 10.3 影响Linux性能的各种因素 10.3.1 系统硬件资源 10.3.2 操作系统相关资源 10.3.3 应用程序软件资源 10.4 系统性能分析标准和优化原则 10.5 几种典型应用对系统资源使用的特点 10.5.1 以静态内容为主的Web应用 10.5.2 以动态内容为主的Web应用 10.5.3 数据库应用 10.5.4 软件下载应用 10.5.5 流媒体服务应用 10.6 Linux下常见的性能分析工具 10.6.1 vmstat命令 10.6.2 sar命令 10.6.3 iostat命令 10.6.4 free命令 10.6.5 uptime命令 10.6.6 netstat命令 10.6.7 top命令 10.7 基于Web应用的性能分析及优化案例 10.7.1 基于动态内容为主的网站优化案例 10.7.2 基于动态、静态内容结合的网站优化案例 10.8 本章小结 第5篇 集群高级应用篇 第11章 构建高可用的LVS负载均衡集群 11.1 LVS集群的组成与特点 11.1.1 LVS集群的组成 11.1.2 LVS集群的特点 11.1.3 LVS集群系统的优缺点 11.2 高可用 LVS负载均衡集群体系结构 11.3 高可用性软件Heartbeat与Keepalived 11.3.1 开源HA软件Heartbeat的介绍 11.3.2 安装heartbeat 11.3.3 开源HA软件Keepalived的介绍 11.3.4 安装Keepalived 11.4 安装LVS软件 11.4.1 配置与检查安装环境 11.4.2 在Director Server上安装IPVS管理软件 11.5 搭建高可用 LVS集群 11.5.1 通过heartbeat搭建LVS高可用性集群 11.5.2 通过Keepalived搭建LVS高可用性集群系统 11.5.3 通过piranha搭建LVS高可用性集群 11.6 测试高可用LVS负载均衡集群系统 11.6.1 高可用性功能测试 11.6.2 负载均衡测试 11.6.3 故障切换测试 11.7 本章小结 第12章 RHCS集群 12.1 RHCS集群概述 12.2 RHCS集群的组成与结构 12.2.1 RHCS集群的组成 12.2.2 RHCS集群结构 12.3 RHCS集群的运行原理及功能 12.3.1 分布式集群管理器(CMAN) 12.3.2 锁管理(DLM) 12.3.3 配置文件管理(CCS) 12.3.4 栅设备(Fence) 12.3.5 高可用性服务管理器 12.3.6 集群配置和管理工具 12.3.7 Redhat GFS 12.4 安装RHCS 12.4.1 安装前准备工作 12.4.2 配置共享存储和RHCS管理端Luci 12.4.3 在集群节点上安装RHCS软件包 12.4.4 在集群节点上安装和配置iSCSI客户端 12.5 配置RHCS高可用集群 12.5.1 创建一个cluster 12.5.2 创建Failover Domain 12.5.3 创建Resources 12.5.4 创建Service 12.5.5 配置存储集群GFS 12.5.6 配置表决磁盘 12.5.7 配置Fence设备 12.6 管理和维护RHCS集群 12.6.1 启动RHCS集群 12.6.2 关闭RHCS集群 12.6.3 管理应用服务 12.6.4 监控RHCS集群状态 12.6.5 管理和维护GFS2文件系统 12.7 RHCS集群功能测试 12.7.1 高可用集群测试 12.7.2 存储集群测试 12.8 本章小结 第13章 Oracle RAC集群 13.1 Oracle集群体系结构 13.2 Oracle ClusterWare体系结构与进程介绍 13.2.1 Oracle ClusterWare 简介 13.2.2 Oracle ClusterWare 进程介绍 13.3 RAC数据库体系结构与进程 13.3.1 RAC 简介 13.3.2 Oracle RAC的特点 13.3.3 RAC进程管理 13.3.4 RAC数据库存储规划 13.4 安装Oracle RAC数据库 13.4.1 安装前的系统配置需求 13.4.2 设置数据库安装资源 13.4.3 配置主机解析文件 13.4.4 检查所需软件包 13.4.5 配置系统内核参数 13.4.6 设置 Shell对Oracle用户的限制 13.4.7 配置hangcheck-timer内核模块 13.4.8 配置系统安全设置 13.4.9 创建Oracle用户和组 13.4.10 设置Oracle用户环境变量 13.4.11 配置节点间SSH信任 13.4.12 配置共享存储系统 13.4.13 安装Oracle Clusterware 13.4.14 安装Oracle数据库 13.4.15 配置Oracle Net 13.4.16 创建RAC数据库 13.5 Oracle CRS的管理与维护 13.5.1 查看集群状态 13.5.2 启动与关闭集群服务资源 13.5.3 启动与关闭CRS 13.5.4 管理voting disk 13.5.5 管理OCR 13.5.6 快速卸载CRS 13.6 ASM基本操作维护 13.6.1 ASM的特点 13.6.2 ASM的体系结构与后台进程 13.6.3 管理ASM实例 13.7 利用srvctl管理RAC数据库 13.7.1 查看实例状态(srvctl status) 13.7.2 查看RAC数据库配置信息(srvctl config) 13.7.3 启动 13.7.4 增加 13.8 测试RAC数据库集群的功能 13.8.1 负载均衡测试 13.8.2 透明应用失败切换测试 13.9 本章小结 第14章 构建MySQL+heartbeat+DRBD+LVS集群应用系统 14.1 MySQL高可用集群概述 14.2 heartbeat + DRBD高可用性方案的实现原理 14.3 部署MySQL高可用高扩展集群 14.3.1 配置之前的准备 14.3.2 DRBD的部署 14.3.3 DRBD的配置 14.3.4 DRBD的维护和管理 14.3.5 DRBD的性能优化 14.3.6 MySQL的部署 14.3.7 heartbeat的部署 14.4 搭建Slave集群 14.4.1 为什么要搭建Slave集群 14.4.2 利用LVS+Keepalived搭建高可用MySQL Slave集群 14.4.3 高可用Slave集群的一些注意点 14.5 部署MySQL集群要考虑的问题 14.6 本章小结

5,530

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 模式及实现
社区管理员
  • 模式及实现社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧