基于普通DLL的插件模式
基于普通 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();