轉(zhuǎn)自http://www.imyaker.com/blog/article.asp?id=34
插件系統(tǒng)-選擇GetProcAddress還是Interfaces(譯)
原文:
Plugin System – an alternative to GetProcAddress and interfaces
代碼下載[介紹]有很多文章描述如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的插件框架,比如后面的鏈接[1]和[2]。通常有兩種方法
(1)
插件實(shí)現(xiàn)一組標(biāo)準(zhǔn)的(并且通常是小的)函數(shù)(方法)。宿主(host)知道這些函數(shù)的名字,并且可以通過(guò)使用GetProcAddress函數(shù)獲得這些函
數(shù)的地址。這并不合適,隨著函數(shù)數(shù)量的增長(zhǎng),維護(hù)變得越來(lái)越困難。你必須手動(dòng)通過(guò)函數(shù)名綁定函數(shù),這樣你就不得不做很多工作。
(2)GetProcAddress所返回的函數(shù)被用來(lái)傳遞接口指針(Interface Pointer)給插件或者從插件里獲取接口指針。剩下的宿主和插件的通信通過(guò)接口完成。下面是一個(gè)例子
我
們將使用一個(gè)簡(jiǎn)單的例子。宿主程序是一個(gè)圖片查看器。它實(shí)現(xiàn)了一個(gè)插件框架來(lái)增加對(duì)不同圖片格式的支持(在這個(gè)例子中就是24-bit的BMP圖象和24
-bit的TGA(Targa)圖象)。插件將被實(shí)現(xiàn)為DLLs并且將有.imp的擴(kuò)展名以便和普通dll文件區(qū)分開(kāi)來(lái)了.注意,盡管如此,可是這篇文章
是關(guān)于插件的,而不是關(guān)于圖象解解析器的。這里所提供的解析器非常基礎(chǔ)并且只是用來(lái)說(shuō)明的。
[使用接口的方法]接口是所有函數(shù)都是公共的并且純虛的基類(lèi),并且沒(méi)有沒(méi)有數(shù)據(jù)成員。比如

程序代碼
// IImageParser is the interface that all image parsers
// must implement
class IImageParser
{
public:
// parses the image file and reads it into a HBITMAP
virtual HBITMAP ParseFile( const char *fname )=0;
// returns true if the file type is supported
virtual bool SupportsType( const char *type ) const=0;
};
實(shí)際的圖象解析器必須繼承自接口類(lèi)并且實(shí)現(xiàn)純虛函數(shù)。BMP文件解析器可能是這個(gè)樣子。

程序代碼
// CBMPParser implements the IImageParser interface
class CBMPParser: public IImageParser
{
public:
virtual HBITMAP ParseFile( const char *fname );
virtual bool SupportsType( const char *type ) const;
private:
HBITMAP CreateBitmap( int width, int height, void **data );
};
static CBMPParser g_BMPParser;
// The host calls this function to get access to the
// image parser
extern "C" __declspec(dllexport) IImageParser *GetParser( void )
{
return &g_BMPParser;
}
宿主將使用LoadLibrary函數(shù)來(lái)載入BmpParser.imp,然后使用GetProcAddress("GetParser")來(lái)得到GetParser函數(shù)的地址,然后調(diào)用它得到IImageParser類(lèi)的指針。
宿主將保存了注冊(cè)了的解析器的鄰接表(list),它把GetParser函數(shù)返回的指針附加到那個(gè)鄰接表上去。
當(dāng)宿主需要解析一個(gè)bmp文件的時(shí)候,它將調(diào)用每個(gè)解析器的SupportType(".BMP")。如果返回類(lèi)型是true,宿主將調(diào)用那個(gè)解析器并且使用完整文件名調(diào)用待解析文件,并將繪制HBITMAP句柄指向的位圖。
基類(lèi)并不真的必須是純接口。在技術(shù)上這里的限制是所有的成員必須可以通過(guò)對(duì)象指針訪問(wèn)。所以你可以有:
- 純虛成員函數(shù)(它們能通過(guò)虛表被間接訪問(wèn))
- 數(shù)據(jù)成員(它們可以通過(guò)對(duì)象的指針直接訪問(wèn))
- 內(nèi)聯(lián)成員函數(shù)(技術(shù)上它們不能通過(guò)指針訪問(wèn),但是它們的代碼又一次在插件里實(shí)例化。
這樣就剩下了非內(nèi)聯(lián)和靜態(tài)成員函數(shù)。插件無(wú)法從宿主訪問(wèn)這樣的函數(shù),宿主也不能對(duì)插件進(jìn)行這樣的操作。不幸的是在一個(gè)大型系統(tǒng)之中,這樣的函數(shù)要占據(jù)代碼的大部分。
例如所有的圖象解析器需要CreateFunction函數(shù)。有必要在基類(lèi)里聲明它并且在宿主端實(shí)現(xiàn)。否則每個(gè)插件都將有一份這個(gè)函數(shù)的拷貝。
這個(gè)方法的另一個(gè)限制是你不能由宿主端暴露任何全局成員或者全局函數(shù)給插件端。
我們?cè)趺锤倪M(jìn)呢?
[把宿主分成Dll和Exe]讓
我們看一下USER32模塊,它有兩個(gè)部分 - user32.dll 和
user32.lib。真正的代碼和數(shù)據(jù)在dll中,lib僅僅提供調(diào)用dll函數(shù)的占位函數(shù)。最好的事情在于它是自動(dòng)發(fā)生的。當(dāng)你鏈接到
user32.lib你就自動(dòng)地獲得訪問(wèn)user32.dll函數(shù)的權(quán)利。(這里翻譯的不好)
MFC 實(shí)現(xiàn)得更進(jìn)一步 - 它暴露你能直接使用和繼承的整個(gè)類(lèi)。它們沒(méi)有我們?cè)谏厦嬗懻摰募兲摻涌陬?lèi)的限制。
我
們也能做同樣的事情。任何你想提供給插件的函數(shù)(我私下覺(jué)得原文functionality這個(gè)詞用得不好)都能放在一個(gè)單獨(dú)的Dll里。使用
/IMPLIB 鏈接器選項(xiàng)來(lái)創(chuàng)建相應(yīng)的 LIB
文件。插件能與那個(gè)靜態(tài)庫(kù)鏈接,并且所有導(dǎo)出函數(shù)都能提供給它們。你能按你喜歡的方式把代碼分成Dll 和
Exe。極限情況下,像在代碼里演示的那樣,Exe 工程里僅僅含有一行WinMain函數(shù),它僅僅用來(lái)啟動(dòng)Dll。
任何你想要導(dǎo)出的全局?jǐn)?shù)據(jù),函數(shù),類(lèi),或者成員函數(shù)必須被標(biāo)記為 __declspec(dllexport) 在編譯插件時(shí)。一個(gè)常用的技巧是使用宏

程序代碼
#ifdef COMPILE_HOST
// when the host is compiling
#define HOSTAPI __declspec(dllexport)
#else
// when the plugins are compiling
#define HOSTAPI __declspec(dllimport)
#endif
添加宏COMPILE_HOST的定義到Dll工程里,但是不加到插件工程里。
在宿主Dll端:

程序代碼
// CImageParser is the base class that all image parsers
// must inherit
class CImageParser
{
public:
// adds the parser to the parsers list
HOSTAPI CImageParser( void );
// parses the image file and reads it into a HBITMAP
virtual HBITMAP ParseFile( const char *fname )=0;
// returns true if the file type is supported
virtual bool SupportsType( const char *type ) const=0;
protected:
HOSTAPI HBITMAP CreateBitmap( int width, int height,
void **data );
};
現(xiàn)在基類(lèi)并不僅僅限于一個(gè)接口。我們能增加更多基本功能。CreateBitmap函數(shù)將被所有解析器共享。
這次不再是宿主調(diào)用一個(gè)函數(shù)來(lái)獲取解析器并且將它添加到鄰接表中,這個(gè)功能被CImageParser的構(gòu)造函數(shù)取代。當(dāng)解析器對(duì)象被創(chuàng)建,它的構(gòu)造函數(shù)將自動(dòng)更新鄰接表。宿主不必再使用GetProcAddress函數(shù)來(lái)看看什么解析器在Dll里。
在插件端:

程序代碼
// CBMPParser inherits from CImageParser
class CBMPParser: public CImageParser
{
public:
virtual HBITMAP ParseFile( const char *fname );
virtual bool SupportsType( const char *type ) const;
};
static CBMPParser g_BMPParser;
當(dāng)g_BMPParser被創(chuàng)建是它的構(gòu)造函數(shù) CBMPParser() 將被調(diào)用。那個(gè)構(gòu)造函數(shù)(在插件端實(shí)現(xiàn))將調(diào)用基類(lèi)的構(gòu)造函數(shù)CImageParser() (在宿主端實(shí)現(xiàn))。那是可能的因?yàn)闃?gòu)造函數(shù)被標(biāo)記為HOSTAPI。
等等,還可以變得更好
[把宿主Dll和Exe連接起來(lái)]
(這一部分暫時(shí)沒(méi)翻譯)
[鏈接][1]
Plug-In framework using DLLs by Mohit Khanna
[2]
ATL COM Based Addin / Plugin Framework With Dynamic Toolbars and Menus by thomas_tom99
PS.我想,Interface方法,是在第一種方法之上加入了一個(gè)間接層。
我現(xiàn)在到?jīng)]有太強(qiáng)的感覺(jué)非內(nèi)聯(lián)函數(shù)和靜態(tài)函數(shù)會(huì)成為這樣一個(gè)大型系統(tǒng)的主要部分,Interface的派生類(lèi)中的非內(nèi)聯(lián)函數(shù)應(yīng)該只被純虛接口函數(shù)調(diào)用,不然就是接口設(shè)計(jì)有問(wèn)題了。