有沒有使用過Adobe Photoshop如果用過你就會對插件的概念比較熟悉對外行人來說插件僅僅是從外部提供給應用程序的代碼塊而已(舉個例子來說在一個DLL中)一個插件和一個普通DLL之間的差異在於插件具有擴展父應用程序功能的能力例如Photoshop本身並不具備進行大量的圖像處理功能插件的加入使其獲得了產生諸如模糊斑點以及其他所有風格的奇怪效果而其中任何一項功能都不是父應用程序自身所具有的
對於圖像處理程序來說這很不錯可是為什麼要花偌大的力氣去完成支持插件的商業應用程序呢?假設我們舉個例子你的應用程序要產生一些報表你的客戶肯定會一直要求更新或者增加新的報表你可以使用一個諸如Report Smith的外部報表生成器這是個不怎麼樣的解決方案需要發布附加的文件要對用戶進行額外的培訓等等你也可以使用QuickReport不過這會使你身處版本控制的噩夢之中——如果每改變一次字體你就要Rebuild你的應用程序的話
然而只要你把報表做到插件中你就可以使用它需要一個新的報表嗎?沒問題只要安裝一個DLL下次應用程序啟動時就會看見它了另外一個例子是處理來自外部設備(比如條形碼掃描器)的數據的應用程序為了給用戶更多的選擇你不得不支持半打的各種設備通過將每種設備接口處理例程寫成插件不用對父應用程序作任何變動就可以獲得最大程度的可伸縮性
入門
在開始寫代碼之前最重要的事情就是搞清楚你的應用程序到底需要擴展哪些功能這是因為插件是通過一個特定的接口與父應用程序交互的而這個接口將根據你的需要來定義在本文中我們將建立個插件以便展示插件與父應用程序相交互的幾種方式
我們將把插件制作成DLL不過在做這項工作之前我們得先制作一個外殼程序來載入和測試它們第一個插件沒有完成什麼大不了的功能實際上它所做的只是返回一個描述自己的字符串不過它證明了很重要的一點——不管有沒有插件應用程序都可以正常運行如果沒有插件它就不會出現在已安裝的插件列表中但是應用程序仍然可以正常的行使功能
我們的插件外殼程序與普通應用程序之間的唯一不同就在於工程源文件中出現在uses子句中的Sharemem單元和加載插件文件的代碼任何在自身與子DLL之間傳遞字符串參數的應用? 都需要Sharemem單元它是DelphiMMdll(Delphi提供該文件)的接口要測試這個外殼需要將DelphiMMdll文件從Delphi\Bin目錄復制到path環境變量所包含的路徑或者應用程序所在目錄中發布最終版本時也需要同時分發夢募插件通過LoadPlugins過程載入到這個測試外殼中這個過程在主窗口的FormCreate事件中調用該過程使用FindFirst和FindNext函數在應用程序所在目錄中查找插件文件找到一個文件以後就使用LoadPlugins過程將其載入
{ 在應用程序目錄下查找插件文件 }
procedure TfrmMainLoadPlugins;
var
sr: TSearchRec;
path: string;
Found: Integer;
begin
path := ExtractFilePath(ApplicationExename);
try
Found := FindFirst(path + cPLUGIN_MASK sr);
while Found = do begin
LoadPlugin(sr);
Found := FindNext(sr);
end;
finally
FindClose(sr);
end;
end;
{ 加載指定的插件 DLL }
procedure TfrmMainLoadPlugin(sr: TSearchRec);
var
Description: string;
LibHandle: Integer;
DescribeProc: TPluginDescribe;
begin
LibHandle := LoadLibrary(Pchar(srName));
if LibHandle $#@;$#@; then
begin
DescribeProc := GetProcAddress(LibHandle cPLUGIN_DESCRIBE);
if Assigned(DescribeProc) then
begin
DescribeProc(Description);
memPluginsLinesAdd(Description);
end
else
begin
MessageDlg(File + srName + is not a valid plugin
mtInformation [mbOK] );
end;
end
else
MessageDlg(An error occurred loading the plugin +
srName + mtError [mbOK] );
end;
LoadPlugin方法展示了插件機制的核心首先插件被寫成DLL其次通過LoadLibrary API它被動態的加載一旦DLL被加載我們就需要一個訪問它所包含的過程和函數的途徑API調用GetProcAddress提供這種機制它返回一個指向所需例程的指針在我們這個簡單的演示中插件僅僅包含一個名為DescribePlugin的過程由常數cPLUGIN_DESCRIBE指定(過程名的大小寫非常重要傳遞到GetProcAddress的名稱必須與包含在DLL中的例程名稱完全一致)如果在DLL中沒有找到請求的例程GetProcAddree將返回nil這樣就允許使用Assigned函數測定返回值
為了以一種易用的方式存儲指向一個函數的指針有必要為用到的變量創建一個特定的類型注意GetProcAddress的返回值被存儲在一個變量中DescribeProc屬於TpluginDescribe類型下面是它的聲明
type
TPluginDescribe = procedure(var Desc: string); stdcall;
由於過程存在於DLL內部它通過標准調用轉換編譯所有導出例程因此需要使用stdcall指示字這個過程使用一個var參數當過程返回的時候它包含插件的描述
要調用剛剛獲得的過程只需要使用保存地址的變量作為過程名後面跟上任何參數就我們的例子而言聲明
DescribeProc(Description)
將會調用在插件中獲得的描述過程並且用描述插件功能的字符串填充Description變量
構造插件
我們已經創建好了父應用程序現在該輪到創建我們希望加載的插件了插件文件是一個標准的Delphi DLL所以我們從Delphi IDE中創建一個新DLL工程保存它由於導出的插件函數將用到字符串參數所以要在工程的uses子句中把Sharemen單元放在最前面
uses
Sharemem SysUtils Classes
main in mainpas;
{$E plg}
exports
DescribePlugin;
begin
end
雖然插件是一個DLL文件但是沒有必要一定要給它一個DLL的擴展名實際上一個原因就足以讓我們有理由改變擴展名當父應用程序尋找要加載的文件時新的擴展名可以作為特定的文件掩模通過使用別的擴展名(我們的例子使用了*plg)你可以在一定程度上確信應用程序只會載入相應的文件編譯指示字$X可以實現這個改變也可以通過Project Options對話框的Application頁來設置擴展名
第一個例子插件的代碼是很簡單的注意DescribePlugin原型與外殼應用程序中的TpluginDescribe類型相一致使用附加的export保留字指定該過程將被導出被導出的過程名稱也將會出現在主工程源代碼的exports段中
unit main;
interface
procedure DescribePlugin(var Desc: string);
export; stdcall;
implementation
procedure DescribePlugin(var Desc: string);
begin
Desc := Test plugin v;
end;
end
在測試這個插件之前要先把它復制到主應用程序的路徑下最簡單的辦法就是在主目錄的子目錄下創建插件然後把輸出路徑設置為主路徑(Project Options對話框的Directories/Conditionals也可以作這個設置)
調試
現在介紹一下Delphi 中一個較好的功能從IDE中調試DLL的能力在DLL工程中可以通過Run paramaters對話框指定某程序為宿主應用程序這就是指向將調用DLL的應用程序的路徑(在我們這個例子中就是剛剛創建的測試外殼程序)然後你就可以在DLL代碼中設置斷點並且按F運行它就像在一個普通應用程序中做的那樣Delphi會運行指定的宿主程序並且通過編譯帶有調試信息的DLL把你指引到DLL代碼內的斷點處
From:http://tw.wingwit.com/Article/program/Delphi/201311/8484.html