程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> Delphi插件(Plug-ins)創建、調試與使用應用程序擴展(一)

Delphi插件(Plug-ins)創建、調試與使用應用程序擴展(一)

編輯:Delphi

       有沒有使用過Adobe Photoshop?如果用過,你就會對插件的概念比較熟悉。
對外行人來說,插件僅僅是從外部提供給應用程序的代碼塊而已(舉個例子來說,在
一個DLL中)。一個插件和一個普通DLL之間的差異在於插件具有擴展父應用程序功能
的能力。例如,Photoshop本身並不具備進行大量的圖像處理功能。插件的加入使其
獲得了產生諸如模糊、斑點,以及其他所有風格的奇怪效果,而其中任何一項功能都
不是父應用程序自身所具有的。
對於圖像處理程序來說這很不錯,可是為什麼要花偌大的力氣去完成支持插件的商業
應用程序呢?假設,我們舉個例子,你的應用程序要產生一些報表。你的客戶肯定會
一直要求更新或者增加新的報表。你可以使用一個諸如Report Smith的外部報表生成
器,這是個不怎麼樣的解決方案,需要發布附加的文件,要對用戶進行額外的培訓,
等等。你也可以使用QuickReport,不過這會使你身處版本控制的噩夢之中--如果每
改變一次字體你就要Rebuild你的應用程序的話。
         然而,只要你把報表做到插件中,你就可以使用它。需要一個新的報表嗎?
沒問題,只要安裝一個DLL,下次應用程序啟動時就會看見它了。另外一個例子是處理
來自外部設備(比如條形碼掃描器)的數據的應用程序,為了給用戶更多的選擇,你
不得不支持半打的各種設備。通過將每種設備接口處理例程寫成插件,不用對父應用
程序作任何變動就可以獲得最大程度的可伸縮性。

入門

        在開始寫代碼之前最重要的事情就是搞清楚你的應用程序到底需要擴展哪
些功能。這是因為插件是通過一個特定的接口與父應用程序交互的,而這個接口將
根據你的需要來定義。在本文中,我們將建立3個插件,以便展示插件與父應用程
序相交互的幾種方式。
        我們將把插件制作成DLL。不過,在做這項工作之前,我們得先制作一個外
殼程序來載入和測試它們。圖1顯示的是加載了第一個插件以後的測試程序。第一個
插件沒有完成什麼大不了的功能,實際上,它所做的只是返回一個描述自己的字符
串。不過,它證明了很重要的一點--不管有沒有插件應用程序都可以正常運行。
如果沒有插件,它就不會出現在已安裝的插件列表中,但是應用程序仍然可以正常
的行使功能。

圖1:插件測試外殼程序

        我們的插件外殼程序與普通應用程序之間的唯一不同就在於工程源文件
中出現在uses子句中的Sharemem單元和加載插件文件的代碼。任何在自身與子DLL
之間傳遞字符串參數的應用程序都需要Sharemem單元,它是DelphiMM.dll(Delphi
提供該文件)的接口。要測試這個外殼,需要將DelphiMM.dll文件從DelphiBin
目錄復制到path環境變量所包含的路徑或者應用程序所在目錄中。發布最終版本
時也需要同時分發該文件。
        插件通過LoadPlugins過程載入到這個測試外殼中,這個過程在主窗口
的FormCreate事件中調用,見圖2。該過程使用FindFirst和FindNext函數在應用
程序所在目錄中查找插件文件。找到一個文件以後,就使用圖3所示的LoadPlugins過
程將其載入。
{ 在應用程序目錄下查找插件文件 }
procedure TfrmMain.LoadPlugins;
var
  sr:     TSearchRec;
  path:   string;
  Found: Integer;
begin
  path := ExtractFilePath(Application.Exename);
   try
    Found := FindFirst(path + cPLUGIN_MASK, 0, sr);
     while Found = 0 do begin
      LoadPlugin(sr);
      Found := FindNext(sr);
     end;
   finally
    FindClose(sr);
   end;
end;

圖 2: 尋找插件
{ 加載指定的插件 DLL. }
procedure TfrmMain.LoadPlugin(sr: TSearchRec);
var
  Description:   string;
  LibHandle:     Integer;
  DescribeProc: TPluginDescribe;
begin
  LibHandle := LoadLibrary(Pchar(sr.Name));
   if LibHandle <> 0 then
   begin
    DescribeProc := GetProcAddress(LibHandle,
                                   cPLUGIN_DESCRIBE);
     if Assigned(DescribeProc) then
       begin
        DescribeProc(Description);
        memPlugins.Lines.Add(Description);
       end
     else
       begin
        MessageDlg(File " + sr.Name +
" is not a valid plug-in.,
          mtInformation, [mbOK], 0);
       end;
   end
   else
    MessageDlg(An error occurred loading the plug-in " +
      sr.Name + "., mtError, [mbOK], 0);
end;

圖 3: 載入插件
        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單元放在最前面。圖4列出的就是我們這個簡單插件的工程源文件。
uses
  Sharemem, SysUtils, Classes,
  main in main.pas;
 
{$E plg.}
 
exports
  DescribePlugin;
 
begin
 
end.

圖 4: 簡單插件的工程源文件
        雖然插件是一個DLL文件,但是沒有必要一定要給它一個.DLL的擴展名。
實際上,一個原因就足以讓我們有理由改變擴展名:當父應用程序尋找要加載的文
件時,新的擴展名可以作為特定的文件掩模。通過使用別的擴展名(我們的例子使
用了*.plg),你可以在一定程度上確信應用程序只會載入相應的文件。編譯指示
字$X可以實現這個改變,也可以通過Project Options對話框的Application頁來設
置擴展名。
        第一個例子插件的代碼是很簡單的。圖5顯示了包含在一個新單元中的代
碼。注意,DescribePlugin原型與外殼應用程序中的TpluginDescribe類型相一致,
使用附加的export保留字指定該過程將被導出。被導出的過程名稱也將會出現在主
工程源代碼的exports段中(在圖4中列出)。
unit main;
 
interface
 
  procedure DescribePlugin(var Desc: string);
     export; stdcall;
 
implementation
 
procedure DescribePlugin(var Desc: string);
begin
  Desc := Test plugin v1.00;
end;
 
end.

圖 5: 例子插件的主程序
        在測試這

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