基于普通
DLL
的插件模式
插件模式已经在软件开发中得到了广泛的应用,这种设计扩展性强,便于插件和主程序独立升级,本文描述了基于普通
DLL
的插件模式的实现方法,并介绍了本人的一些经验。
1.
原理和特点
基于普通
DLL
实现插件模式的原理:利用
LoadLibrary
打开指定的动态链接库,然后用
GetProcAddress
取得库中指定函数的地址并调用其功能。设计插件时应把功能分类并制定接口,插件管理器针对接口编程。
举个例子,某类插件负责对数据的读和写,现制定接口:
BOOL read(void *pIn);
BOOL write(void *pOut);
我们在主程序中要调用指定插件的读功能,可使用下面代码(略去对返回值的检验):
typedef BOOL (*FN_Read)(void *pIn);
FN_Read fnRead = NULL;
HMODULE hDll = LoadLibrary(“plugin\test.dll”);
fnRead = (FN_Read) GetProcAddress(hDll, “read”);
BOOL bRet = fnRead(xxxxx);
使用这种方法实现插件模式,不需要注册表的参与,便于达到软件“绿色”的要求,但管理器调用具体插件的时候,要指定路径(一般是固定的)。
2.
实现方法
一般实际应用中不会直接采用上面例子中的方法,因为不易扩展,而且复用性也差。不同的人有自己不同的实现方法,下面我来讲一下我的方法。
l
首先,定义一个“插件成员”基类,用来传递插件
DLL
中特定功能函数(接口)的指针。
//--------------------------------
//
描述
:
插件成员类
//--------------------------------
class CPluginMember
{
public:
void (*fnOnOpenPlugin)(void *pParam);
void (*fnOnClosePlugin)(void *pParam);
CPluginMember()
{
fnOnOpenPlugin = NULL;
fnOnClosePlugin = NULL;
};
};
这个类包括两个函数指针,分别是打开插件和关闭插件时要调用的,默认值为空。
l
然后,自己实现一个插件管理器,负责从
DLL
中获得特定的“插件成员”对象,如果这个过程成功,那么就可以通过该“插件成员”对象调用插件的功能了。
//-------------------------------------------------------------
//DESC :
打开插件
//
//lpszPluginDLL :
插件文件名
//pParam :
参数指针
//
//RETURN :
成功
TRUE
失败
FALSE
//-------------------------------------------------------------
BOOL CPluginManager::Open(LPCTSTR lpszPluginDLL, void *pParam)
{
//
加载
DLL
m_hDll = LoadLibrary(lpszPluginDLL);
if (!m_hDll)
{
//
加载
DLL
失败
_RPT0(_CRT_WARN, "Cannot Load plugin file.\n");
return FALSE;
}
else
{
FN_GetPlugin fnGetPlugin;
//
取得
GetPlugin
地址
fnGetPlugin =(FN_GetPlugin)GetProcAddress(m_hDll, PLUGIN_INTERFACE);
if (NULL == fnGetPlugin)
{
//
取得
GetPlugin
地址失败
_RPT0(_CRT_WARN, "Cannot retrieve GetPlugin()'s handle.\n");
return FALSE;
}
else
{
try
{
//
取得插件的
(
函数
)
成员
m_pPlugin = fnGetPlugin();
if(!m_pPlugin)
{
_RPT0(_CRT_WARN, "Cannot get plugin members.\n");
return FALSE;
}
//
执行初始化操作
if(m_pPlugin->fnOnOpenPlugin)
{
m_pPlugin->fnOnOpenPlugin(pParam);
}
}
catch(...)
{
//
取得插件成员发生错误,如类型不匹配
_RPT0(_CRT_WARN, "Error occured when handle plugin members.\n");
return FALSE;
}
return TRUE;
}
}
};
//-------------------------------------------------------------
//DESC :
关闭插件
//
//pParam :
参数指针
//
//RETURN :
成功
TRUE
失败
FALSE
//-------------------------------------------------------------
BOOL CPluginManager::Close(void *pParam)
{
//
执行关闭前的清除操作
if(!m_pPlugin)
{
_RPT0(_CRT_WARN, "No plugin is open, Ignore closing operation\n");
return FALSE;
}
if(m_pPlugin->fnOnClosePlugin)
{
m_pPlugin->fnOnClosePlugin(pParam);
}
m_pPlugin = NULL;
BOOL bRet = FreeLibrary(m_hDll);
m_hDll = NULL; //
说明插件关闭
return bRet;
};
//-------------------------------------------------------------
//DESC :
判断插件是否处于打开状态
//
//RETURN :
处于打开返回
TRUE
,否则
FALSE
//-------------------------------------------------------------
BOOL CPluginManager::IsOpen()
{
return (m_hDll != NULL);
};
有了这两个类,就可以实现基本的插件模型了,其它的功能(比如插件的搜索、类型判断)都可以在这个基础上衍生出来。这种方法已经成功应用到一个图像处理组件中,为开发提供了不少便利。
3.
使用方法
在插件
DLL
工程中:
l
继承
CPluginMember
并加入所需接口
class CPluginMemberTest : public CPluginMember
{
public:
void (*fnTest)();
};
l
定义一个“插件成员”的全局变量
g_plgTest
,和一个名为
GetPlugin
的函数(可导出),
GetPlugin
任务是把
g_plgTest
返回给主程序中的调用者
CPluginMemberTest g_plgTest;
__declspec(dllexport) CPluginMember* GetPlugin();
CPluginMember* GetPlugin()
{
return (CPluginMember*)&g_plgTest;
};
l
将实现指定功能函数的地址,在
DLL
初始化的时候,保存到
g_plgTest
中
BOOL CTestDllApp::InitInstance()
{
CWinApp::InitInstance();
g_plgTest.fnOnClosePlugin = NULL;
g_plgTest.fnOnOpenPlugin = NULL;
g_plgTest.fnTest = TestFunc;
return TRUE;
}
在主程序工程中使用下述代码即可调用插件的功能了:
CPluginMember member;
CPluginManager manager;
BOOL bRlt = manager.Open(_T(“PluginFullPath”), NULL);
CPluginMemberTest *plgTest = (CPluginMemberTest*)manager.m_pPlugin;
plgTest->fnTest();
manager.Close();