目前,不少流行軟件都提供有對外掛插件的支持功能,如Winamp、Realplay等等。這些軟件通過對插件技術(shù)的使用為日后的軟件升級(jí)和功能擴(kuò)展提供了相當(dāng)?shù)谋憷麠l件。尤為重要的是,通過使用插件技術(shù),使得對軟件的功能擴(kuò)展將不再完全受限于軟件廠商,任何第三方開發(fā)商或是程序員個(gè)人只要遵循了軟件提供的插件接口標(biāo)準(zhǔn)去開發(fā)插件就完全可以同主體軟件有很好的兼容,從而使用戶對應(yīng)用程序進(jìn)行個(gè)性化功能擴(kuò)展成為了可能?;诓寮夹g(shù)的以上諸多優(yōu)勢,本文下面將圍繞插件的制作、應(yīng)用程序?qū)Σ寮闹С值染唧w問題對其展開討論。
設(shè)計(jì)思路及插件接口標(biāo)準(zhǔn)
通常支持插件的應(yīng)用程序多將外掛擴(kuò)展插件集中放置于某個(gè)指定的目錄下,程序執(zhí)行時(shí)首先在此目錄下搜尋是否有插件存在,如有則為插件將其插入到應(yīng)用程序,應(yīng)用程序在終止運(yùn)行時(shí)負(fù)責(zé)將插件釋放。
至于插件以何種形式提供則沒有固定的規(guī)定,可以是獨(dú)立的應(yīng)用程序,也可以是動(dòng)態(tài)鏈接庫或是其他一些文件格式,不管插件具體以何種形式提供,都是以方便使用為目的。本文即以使用較為靈活的動(dòng)態(tài)鏈接庫作為插件的提供形式,動(dòng)態(tài)鏈接庫通過外部導(dǎo)出函數(shù)為應(yīng)用程序提供對插件功能的調(diào)用,應(yīng)用程序在對動(dòng)態(tài)鏈接庫進(jìn)行動(dòng)態(tài)裝載時(shí)也比較容易實(shí)現(xiàn)。這里與以往對動(dòng)態(tài)鏈接庫的使用有所不同,通常的應(yīng)用程序事先已經(jīng)明確知道需要使用哪些動(dòng)態(tài)鏈接庫,動(dòng)態(tài)鏈接庫又提供有哪些函數(shù)等信息,而允許使用插件的應(yīng)用程序在發(fā)布時(shí)則無法預(yù)知在軟件發(fā)布后第三方開發(fā)商將會(huì)開發(fā)出多少插件、插件都提供有什么功能函數(shù)等。因此這就需要在容許插件的應(yīng)用程序和插件之間建立一種統(tǒng)一的接口標(biāo)準(zhǔn)并通過此接口標(biāo)準(zhǔn)完成對所有后期插件的管理。在此,主程序和插件之間是通過一個(gè)標(biāo)準(zhǔn)的DLL導(dǎo)出函數(shù)來實(shí)現(xiàn)的,主要用于在主體程序內(nèi)插件對象的創(chuàng)建:
BOOL Plug_CreateObject(void ** pobj)
{
*pobj = new CPlugA;
return *pobj != NULL;
}
其中類CPlugA是在動(dòng)態(tài)鏈接庫中由基類CPlugBase派生出來的,提供有插件的大部分主要功能,如插件圖標(biāo)的獲取、插件提供的功能接口函數(shù)以及插件的釋放等?;怌PlugBase的結(jié)構(gòu)如下:
class CPlugBase
{
public:
CPlugBase(){};
virtual HICON GetIcon() = 0;
virtual void Interface(int k) = 0;
virtual void Release() = 0;
};
考慮到主體程序無法預(yù)知待插入的插件數(shù)目,為管理插件對象方便,通過模板類CArray完成對各個(gè)插件對象的存儲(chǔ)與管理,此模板類所管理的數(shù)組為PLUG_ST結(jié)構(gòu)對象。PLUG_ST結(jié)構(gòu)記錄了插件類提供的的CPlugBase型指針和作為插件載體的動(dòng)態(tài)鏈接庫的實(shí)例句柄,其具體定義如下:
typedef struct{
CPlugBase * pObj;
HINSTANCE hIns;
}PLUG_ST, * LPPLUG_ST;
另外,在程序界面上,每向應(yīng)用程序添加一個(gè)新的插件,都應(yīng)當(dāng)在主程序的界面上增添與之相關(guān)聯(lián)的按鈕或菜單等,以便用戶可以通過位于主程序界面上的按鈕或菜單實(shí)現(xiàn)對插件內(nèi)部功能函數(shù)的調(diào)用。本文在此是通過向工具條增添按鈕的方式來達(dá)到此目的的,按鈕上的圖標(biāo)由插件提供,應(yīng)用程序通過插件類的GetIcon()函數(shù)獲取到圖標(biāo)句柄,并將其繪制在工具條按鈕上。
為普通應(yīng)用程序擴(kuò)展插件支持功能
插件支持功能并非Winamp、RealPlay等大牌軟件所獨(dú)有,任何普通應(yīng)用程序經(jīng)過程序編碼均可將其擴(kuò)展為支持插件的應(yīng)用程序。通常將這部分?jǐn)U展代碼在主框架類中完成,根據(jù)前面所述思路,首先從應(yīng)用程序所在目錄下搜尋子目錄PLUGINS下是否存在以動(dòng)態(tài)鏈接庫形式提供的插件,如果在此目錄下沒有找到動(dòng)態(tài)鏈接庫那么就說明當(dāng)前還沒有插件,因此程序也就不需要做進(jìn)一步處理,如果找到插件,就一一將其插入到應(yīng)用程序。搜尋插件的部分代碼如下:
……
GetModuleFileName(NULL, filename, MAX_PATH); // 獲取應(yīng)用程序路徑
strPath = CString(filename); //設(shè)定當(dāng)前目錄下的子目錄PLUGINS
strPath = strPath.Left(strPath.GetLength() -CString(AfxGetAppName()).GetLength() - 4) +CString("PLUGINS");
CString strFindFile = strPath + "http://*.dll";
// 搜尋子目錄PLUGINS下的所有動(dòng)態(tài)鏈接庫
WIN32_FIND_DATA wfd;
HANDLE hf = FindFirstFile(strFindFile, &wfd); //尋找第一個(gè)
if (hf != INVALID_HANDLE_VALUE)
{
// 如發(fā)現(xiàn)插件就將其插入到本應(yīng)用程序
CreatePlug(strPath + "http://" + wfd.cFileName);
while (FindNextFile(hf, &wfd)) //繼續(xù)尋找下一個(gè)
CreatePlug(strPath + "http://" + wfd.cFileName);
FindClose(hf); // 結(jié)束搜尋
}
其中,CreatePlug()函數(shù)負(fù)責(zé)將插件裝載到應(yīng)用程序,其參數(shù)指定了待裝載的插件的絕對路徑。在實(shí)現(xiàn)時(shí),首先通過LoadLibrary()函數(shù)將插件模塊裝載到內(nèi)存,并將獲取到的實(shí)例句柄保存到PLUG_ST結(jié)構(gòu)的hIns中,最后將此結(jié)構(gòu)對象添加到CArray模板類對象m_arrPlugObj中,主要實(shí)現(xiàn)代碼如下:
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);
同用戶交互部分,則采取這樣的處理:將所有插件的圖標(biāo)從插件動(dòng)態(tài)鏈接庫中提取出來,并放置于圖象列表,最后在浮動(dòng)工具條上創(chuàng)建對應(yīng)的按鈕并將插件圖標(biāo)繪制其上。同樣也是出于對后期插件的不可預(yù)知性,在工具條上創(chuàng)建按鈕的資源ID從ID_PLUG_POINTER開始,依次累加。具體實(shí)現(xiàn)可參考如下代碼:
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 whenbutton 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);
}
對于各個(gè)插件按鈕的命令響應(yīng)也不能以通常的ON_COMMAND宏執(zhí)行命令映射,而要以O(shè)N_COMMAND_RANGE宏實(shí)現(xiàn)對一個(gè)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())
{
// 調(diào)用對應(yīng)插件的功能函數(shù)。
if (m_arrPlugObj[id].pObj)
m_arrPlugObj[id].pObj->Interface(id);
}
}
為保證系統(tǒng)資源的有效釋放,在程序終止之前必須確保將加載過的所有插件資源予以釋放:
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();
至此,只要應(yīng)用程序在PLUGINS子目錄下發(fā)現(xiàn)了插件動(dòng)態(tài)鏈接庫的存在,就會(huì)將其裝載到程序并通過工具條按鈕完成用戶同新添加插件的交互。如要從程序去掉某個(gè)插件只需在插件目錄下將對應(yīng)的插件模塊刪除即可。
插件的制作
插件的制作其實(shí)就是對動(dòng)態(tài)鏈接庫的創(chuàng)建,因此總的來說比較簡單,但是作為插件載體的動(dòng)態(tài)鏈接庫與普通的動(dòng)態(tài)鏈接庫還是有一些區(qū)別的。例如,插件需要為主體應(yīng)用程序提供圖標(biāo),因此不僅在資源中要引入插件圖標(biāo),而且在編譯時(shí)還要將其設(shè)置為"UseMFC in a StaticLibrary"以便在編譯時(shí)能將所有的資源打包到插件模塊,否則在應(yīng)用程序插入插件時(shí)將無法在工具條按鈕上繪制圖標(biāo)。插件在創(chuàng)建時(shí)同樣也必須遵循其同主體程序的接口標(biāo)準(zhǔn),這主要通過導(dǎo)出函數(shù)來體現(xiàn)的:
LIBRARY "PlugA"
DESCRIPTION 'PlugA Windows Dynamic Link Library'
EXPORTS
Plug_CreateObject @1
導(dǎo)出函數(shù)Plug_CreateObject負(fù)責(zé)在應(yīng)用程序中創(chuàng)建一個(gè)插件對象:
BOOL WINAPI Plug_CreateObject(void ** pobj)
{
*pobj = new CPlugA;
return *pobj != NULL;
}
在前面已經(jīng)提到過,CPlugA是基類CPlugBase的一個(gè)派生類,可以根據(jù)需要對CPlugBase的幾個(gè)虛函數(shù)進(jìn)行重載,以實(shí)現(xiàn)本插件所獨(dú)有的一些功能。另外,由于主體程序是通過GetIcon()來獲取插件圖標(biāo)的,因此必須在動(dòng)態(tài)鏈接庫被加載時(shí)首先通過LoadIcon()函數(shù)將圖標(biāo)裝載到內(nèi)存并保存其句柄于m_hIcon,等待主程序通過GetIcon()函數(shù)來獲取,該句柄的釋放在當(dāng)動(dòng)態(tài)鏈接庫被釋放時(shí)由函數(shù)DeleteObject()來執(zhí)行。
小結(jié)
通過前述方法可以為普通應(yīng)用程序添加插件支持功能,并可以在軟件發(fā)布后以插件的形式對軟件進(jìn)行功能上的擴(kuò)展,操作過程也比較靈活方便。由于經(jīng)過這種擴(kuò)展,使軟件的各大功能模塊分布于不同的插件,在軟件升級(jí)或維護(hù)時(shí)只需對相應(yīng)的插件進(jìn)行替換即可,這對軟件的升級(jí)維護(hù)可以起到積極的作用。本文所述程序在Windows98下由Microsoft Visual C++ 6.0編譯通過。
愛華網(wǎng)



