天天看點

VC++實作插件程式設計

  發表日期:2006年5月15日   出處:yesky    作者:靈山道人     【編輯錄入:webmaster】 

引言

  目前,不少流行軟體都提供有對外挂插件的支援功能,如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()來執行。

  小結

  通過前述方法可以為普通應用程式添加插件支援功能,并可以在軟體釋出後以插件的形式對軟體進行功能上的擴充,操作過程也比較靈活友善。由于經過這種擴充,使軟體的各大功能子產品分布于不同的插件,在軟體更新或維護時隻需對相應的插件進行替換即可,這對軟體的更新維護可以起到積極的作用。本文所述程式在Windows 98下由Microsoft Visual C++ 6.0編譯通過。

繼續閱讀