设计思路及插件接口标准
通常支持插件的应用程序多将外挂扩展插件集中放置于某个指定的目录下,程序执行时首先在此目录下搜寻是否有插件存在,如有则为插件将其插入到应用程序,应用程序在终止运行时负责将插件释放。
BOOL Plug_CreateObject(void ** pobj)
{
*pobj = new CPlugA;
return *pobj != NULL;
}
其中类CPlugA是在动态链接库中由基类CPlugBase派生出来的,提供有插件的大部分主要功能,如插件图标的获取、插件提供的功能接口函数以及插件的释放等。基类CPlugBase的结构如下:
class CPlugBase
{
public:
CPlugBase(){};
virtual HICON GetIcon() = 0;
virtual void Interface(int k) = 0;
virtual void Release() = 0;
};
考虑到主体程序无法预知待插入的插件数目,为管理插件对象方便, 通过模板类CArray完成对各个插件对象的存储与管理,此模板类所管理的数组为PLUG_ST结构对象。PLUG_ST结构记录了插件类提供的的CPlugBase型指针和作为插件载体的动态链接库的实例句柄,其具体定义如下:
typedef struct{
CPlugBase * pObj;
HINSTANCE hIns;
}PLUG_ST, * LPPLUG_ST;
插件支持功能并非Winamp、RealPlay等大牌软件所独有,任何普通应用程序经过程序编码均可将其扩展为支持插件的应用程序。通常将这部分扩展代码在主框架类中完成,根据前面所述思路,首先从应用程序所在目录下搜寻子目录PLUGINS下是否存在以动态链接库形式提供的插件,如果在此目录下没有找到动态链接库那么就说明当前还没有插件,因此程序也就不需要做进一步处理,如果找到插件,就一一将其插入到应用程序。搜寻插件的部分代码如下:
……
GetModuleFileName(NULL, filename, MAX_PATH); // 获取应用程序路径
strPath = CString(filename); //设定当前目录下的子目录PLUGINS
strPath = strPath.Left(strPath.GetLength() – CString(AfxGetAppName()).GetLength() – 4) + CString(“PLUGINS”);
CString strFindFile = strPath + “//*.dll”;
// 搜寻子目录PLUGINS下的所有动态链接库
WIN32_FIND_DATA wfd;
HANDLE hf = FindFirstFile(strFindFile, &wfd); //寻找第一个
if (hf != INVALID_HANDLE_VALUE)
{
// 如发现插件就将其插入到本应用程序
CreatePlug(strPath + “//” + wfd.cFileName);
while (FindNextFile(hf, &wfd)) //继续寻找下一个
CreatePlug(strPath + “//” + wfd.cFileName);
FindClose(hf); // 结束搜寻
}
其中,CreatePlug()函数负责将插件装载到应用程序,其参数指定了待装载的插件的绝对路径。在实现时,首先通过LoadLibrary()函数将插件模块装载到内存,并将获取到的实例句柄保存到PLUG_ST结构的hIns中,最后将此结构对象添加到CArray模板类对象m_arrPlugObj中,主要实现代码如下:
PLUG_ST stPs;
ZeroMemory(&stPs, sizeof(stPs));
stPs.hIns = LoadLibrary(szPlug);
PFN_Plug_CreateObject pFunc = (PFN_Plug_CreateObject)GetProcAddress(stPs.hIns, _T(“Plug_CreateObject”));
if (pFunc((void **)&stPs.pObj))
m_arrPlugObj.Add(stPs);
同用户交互部分,则采取这样的处理:将所有插件的图标从插件动态链接库中提取出来,并放置于图象列表,最后在浮动工具条上创建对应的按钮并将插件图标绘制其上。同样也是出于对后期插件的不可预知性,在工具条上创建按钮的资源ID从ID_PLUG_POINTER开始,依次累加。具体实现可参考如下代码:
int size = m_arrPlugObj.GetSize();
m_ImageList.Create(16, 16, ILC_COLOR32, size + 1, size);
for (int i = 0; i < size; i ++)
m_ImageList.Add(m_arrPlugObj[i].pObj->GetIcon());
CToolBarCtrl& ctrlBar = m_wndPlugBar.GetToolBarCtrl();
ctrlBar.SetImageList(&m_ImageList);
TBBUTTON btn;
for (i = 0; i < size; i ++)
{
btn.iBitmap = i;
btn.idCommand = ID_PLUG_POINTER + i;//command to be sent when button pressed
btn.fsState = TBSTATE_ENABLED; //button state–see below
btn.fsStyle = TBSTYLE_BUTTON; //button style–see below
btn.dwData = 0; //application-defined value
btn.iString = NULL; //zero-based index of button label string
ctrlBar.AddButtons(1, &btn);
}
对于各个插件按钮的命令响应也不能以通常的ON_COMMAND宏执行命令映射,而要以ON_COMMAND_RANGE宏实现对一个ID范围的命令映射:
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
……
ON_COMMAND_RANGE(ID_PLUG_POINTER, ID_PLUG_POINTER+256, OnPlugHit)
END_MESSAGE_MAP()
……
void CMainFrame::OnPlugHit(UINT nID)
{
int – ID_PLUG_POINTER;
if (id >= 0 && id < m_arrPlugObj.GetSize())
{
// 调用对应插件的功能函数。
if (m_arrPlugObj[id].pObj)
m_arrPlugObj[id].pObj->Interface(id);
}
}
为保证系统资源的有效释放,在程序终止之前必须确保将加载过的所有插件资源予以释放:
for (int i = 0; i < m_arrPlugObj.GetSize(); i++)
{
if (m_arrPlugObj[i].pObj)
m_arrPlugObj[i].pObj->Release();
if (m_arrPlugObj[i].hIns)
FreeLibrary(m_arrPlugObj[i].hIns);
}
m_arrPlugObj.RemoveAll();
至此,只要应用程序在PLUGINS子目录下发现了插件动态链接库的存在,就会将其装载到程序并通过工具条按钮完成用户同新添加插件的交互。如要从程序去掉某个插件只需在插件目录下将对应的插件模块删除即可。
插件的制作
插件的制作其实就是对动态链接库的创建,因此总的来说比较简单,但是作为插件载体的动态链接库与普通的动态链接库还是有一些区别的。例如,插件需要为主体应用程序提供图标,因此不仅在资源中要引入插件图标,而且在编译时还要将其设置为”Use MFC in a Static Library”以便在编译时能将所有的资源打包到插件模块,否则在应用程序插入插件时将无法在工具条按钮上绘制图标。插件在创建时同样也必须遵循其同主体程序的接口标准,这主要通过导出函数来体现的:
LIBRARY “PlugA”
DESCRIPTION ‘PlugA Windows Dynamic Link Library’
EXPORTS
Plug_CreateObject @1
导出函数Plug_CreateObject负责在应用程序中创建一个插件对象:
BOOL WINAPI Plug_CreateObject(void ** pobj)
{
*pobj = new CPlugA;
return *pobj != NULL;
}
在前面已经提到过,CPlugA是基类CPlugBase的一个派生类,可以根据需要对CPlugBase的几个虚函数进行重载,以实现本插件所独有的一些功能。另外,由于主体程序是通过GetIcon()来获取插件图标的,因此必须在动态链接库被加载时首先通过LoadIcon()函数将图标装载到内存并保存其句柄于m_hIcon,等待主程序通过GetIcon()函数来获取,该句柄的释放在当动态链接库被释放时由函数DeleteObject()来执行。
小结
通过前述方法可以为普通应用程序添加插件支持功能,并可以在软件发布后以插件的形式对软件进行功能上的扩展,操作过程也比较灵活方便。由于经过这种扩展,使软件的各大功能模块分布于不同的插件,在软件升级或维护时只需对相应的插件进行替换即可,这对软件的升级维护可以起到积极的作用。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!