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

程序代碼
// 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;
};
實際的圖象解析器必須繼承自接口類并且實現純虛函數。BMP文件解析器可能是這個樣子。

程序代碼
// 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函數來載入BmpParser.imp,然后使用GetProcAddress("GetParser")來得到GetParser函數的地址,然后調用它得到IImageParser類的指針。
宿主將保存了注冊了的解析器的鄰接表(list),它把GetParser函數返回的指針附加到那個鄰接表上去。
當宿主需要解析一個bmp文件的時候,它將調用每個解析器的SupportType(".BMP")。如果返回類型是true,宿主將調用那個解析器并且使用完整文件名調用待解析文件,并將繪制HBITMAP句柄指向的位圖。
基類并不真的必須是純接口。在技術上這里的限制是所有的成員必須可以通過對象指針訪問。所以你可以有:
- 純虛成員函數(它們能通過虛表被間接訪問)
- 數據成員(它們可以通過對象的指針直接訪問)
- 內聯成員函數(技術上它們不能通過指針訪問,但是它們的代碼又一次在插件里實例化。
這樣就剩下了非內聯和靜態成員函數。插件無法從宿主訪問這樣的函數,宿主也不能對插件進行這樣的操作。不幸的是在一個大型系統之中,這樣的函數要占據代碼的大部分。
例如所有的圖象解析器需要CreateFunction函數。有必要在基類里聲明它并且在宿主端實現。否則每個插件都將有一份這個函數的拷貝。
這個方法的另一個限制是你不能由宿主端暴露任何全局成員或者全局函數給插件端。
我們怎么改進呢?
[把宿主分成Dll和Exe]讓
我們看一下USER32模塊,它有兩個部分 - user32.dll 和
user32.lib。真正的代碼和數據在dll中,lib僅僅提供調用dll函數的占位函數。最好的事情在于它是自動發生的。當你鏈接到
user32.lib你就自動地獲得訪問user32.dll函數的權利。(這里翻譯的不好)
MFC 實現得更進一步 - 它暴露你能直接使用和繼承的整個類。它們沒有我們在上面討論的純虛接口類的限制。
我
們也能做同樣的事情。任何你想提供給插件的函數(我私下覺得原文functionality這個詞用得不好)都能放在一個單獨的Dll里。使用
/IMPLIB 鏈接器選項來創建相應的 LIB
文件。插件能與那個靜態庫鏈接,并且所有導出函數都能提供給它們。你能按你喜歡的方式把代碼分成Dll 和
Exe。極限情況下,像在代碼里演示的那樣,Exe 工程里僅僅含有一行WinMain函數,它僅僅用來啟動Dll。
任何你想要導出的全局數據,函數,類,或者成員函數必須被標記為 __declspec(dllexport) 在編譯插件時。一個常用的技巧是使用宏

程序代碼
#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 );
};
現在基類并不僅僅限于一個接口。我們能增加更多基本功能。CreateBitmap函數將被所有解析器共享。
這次不再是宿主調用一個函數來獲取解析器并且將它添加到鄰接表中,這個功能被CImageParser的構造函數取代。當解析器對象被創建,它的構造函數將自動更新鄰接表。宿主不必再使用GetProcAddress函數來看看什么解析器在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;
當g_BMPParser被創建是它的構造函數 CBMPParser() 將被調用。那個構造函數(在插件端實現)將調用基類的構造函數CImageParser() (在宿主端實現)。那是可能的因為構造函數被標記為HOSTAPI。
等等,還可以變得更好
[把宿主Dll和Exe連接起來]
(這一部分暫時沒翻譯)
[鏈接][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方法,是在第一種方法之上加入了一個間接層。
我現在到沒有太強的感覺非內聯函數和靜態函數會成為這樣一個大型系統的主要部分,Interface的派生類中的非內聯函數應該只被純虛接口函數調用,不然就是接口設計有問題了。