引言 html
目前,很多流行软件都提供有对外挂插件的支持功能,如Winamp、Realplay等等。这些软件经过对插件技术的使用为往后的软件升级和功能扩展提供了至关的便利条件。 尤其重要的是,经过使用插件技术,使得对软件的功能扩展将再也不彻底受限于软件厂商,任何第三方开发商或是程序员我的只要遵循了软件提供的插件接口标准去开发插件就彻底能够同主体软件有很好的兼容,从而使用户对应用程序进行个性化功能扩展成为了可能。基于插件技术的以上诸多优点,本文下面将围绕插件的制做、应用程序对插件的支持等具体问题对其展开讨论。程序员
设计思路及插件接口标准数组
一般支持插件的应用程序多将外挂扩展插件集中放置于某个指定的目录下,程序执行时首先在此目录下搜寻是否有插件存在,若有则为插件将其插入到应用程序,应用程序在终止运行时负责将插件释放。app
至于插件以何种形式提供则没有固定的规定,能够是独立的应用程序,也能够是动态连接库或是其余一些文件格式,无论插件具体以何种形式提供,都是以方便使用为目的。本文即以使用较为灵活的动态连接库做为插件的提供形式,动态连接库经过外部导出函数为应用程序提供对插件功能的调用,应用程序在对动态连接库进行动态装载时也比较容易实现。这里与以往对动态连接库的使用有所不一样,一般的应用程序事先已经明确知道须要使用哪些动态连接库,动态连接库又提供有哪些函数等信息,而容许使用插件的应用程序在发布时则没法预知在软件发布后第三方开发商将会开发出多少插件、插件都提供有什么功能函数等。所以这就须要在允许插件的应用程序和插件之间创建一种统一的接口标准并经过此接口标准完成对全部后期插件的管理。在此,主程序和插件之间是经过一个标准的DLL导出函数来实现的,主要用于在主体程序内插件对象的建立:框架
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;
post
另外,在程序界面上,每向应用程序添加一个新的插件,都应当在主程序的界面上增添与之相关联的按钮或菜单等,以便用户能够经过位于主程序界面上的按钮或菜单实现对插件内部功能函数的调用。本文在此是经过向工具条增添按钮的方式来达到此目的的,按钮上的图标由插件提供,应用程序经过插件类的GetIcon()函数获取到图标句柄,并将其绘制在工具条按钮上。编码
为普通应用程序扩展插件支持功能插件
插件支持功能并不是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开始,依次累加。具体实现可参考以下代码:
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 = nID - 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()来执行。
小结
经过前述方法能够为普通应用程序添加插件支持功能,并能够在软件发布后以插件的形式对软件进行功能上的扩展,操做过程也比较灵活方便。因为通过这种扩展,使软件的各大功能模块分布于不一样的插件,在软件升级或维护时只需对相应的插件进行替换便可,这对软件的升级维护能够起到积极的做用。本文所述程序在Windows 98下由Microsoft Visual C++ 6.0编译经过。