程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> vc教程 >> VC++實現應用程序對插件的支持

VC++實現應用程序對插件的支持

編輯:vc教程
 目前,不少流行軟件都提供有對外掛插件的支持功能,如Winamp、Realplay等等。這些軟件通過對插件技術的使用為日後的軟件升級和功能擴展提供了相當的便利條件。尤為重要的是,通過使用插件技術,使得對軟件的功能擴展將不再完全受限於軟件廠商,任何第三方開發商或是程序員個人只要遵循了軟件提供的插件接口標准去開發插件就完全可以同主體軟件有很好的兼容,從而使用戶對應用程序進行個性化功能擴展成為了可能。基於插件技術的以上諸多優勢,本文下面將圍繞插件的制作、應用程序對插件的支持等具體問題對其展開討論。

  設計思路及插件接口標准

  通常支持插件的應用程序多將外掛擴展插件集中放置於某個指定的目錄下,程序執行時首先在此目錄下搜尋是否有插件存在,如有則為插件將其插入到應用程序,應用程序在終止運行時負責將插件釋放。

  至於插件以何種形式提供則沒有固定的規定,可以是獨立的應用程序,也可以是動態鏈接庫或是其他一些文件格式,不管插件具體以何種形式提供,都是以方便使用為目的。本文即以使用較為靈活的動態鏈接庫作為插件的提供形式,動態鏈接庫通過外部導出函數為應用程序提供對插件功能的調用,應用程序在對動態鏈接庫進行動態裝載時也比較容易實現。這裡與以往對動態鏈接庫的使用有所不同,通常的應用程序事先已經明確知道需要使用哪些動態鏈接庫,動態鏈接庫又提供有哪些函數等信息,而允許使用插件的應用程序在發布時則無法預知在軟件發布後第三方開發商將會開發出多少插件、插件都提供有什麼功能函數等。因此這就需要在容許插件的應用程序和插件之間建立一種統一的接口標准並通過此接口標准完成對所有後期插件的管理。在此,主程序和插件之間是通過一個標准的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;


  另外,在程序界面上,每向應用程序添加一個新的插件,都應當在主程序的界面上增添與之相關聯的按鈕或菜單等,以便用戶可以通過位於主程序界面上的按鈕或菜單實現對插件內部功能函數的調用。本文在此是通過向工具條增添按鈕的方式來達到此目的的,按鈕上的圖標由插件提供,應用程序通過插件類的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開始,依次累加。具體實現可參考如下代碼:

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 = 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()來執行。

  小結

  通過前述方法可以為普通應用程序添加插件支持功能,並可以在軟件發布後以插件的形式對軟件進行功能上的擴展,操作過程也比較靈活方便。由於經過這種擴展,使軟件的各大功能模塊分布於不同的插件,在軟件升級或維護時只需對相應的插件進行替換即可,這對軟件的升級維護可以起到積極的作用。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved