欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

初探Delphi中的插件編程

 更新時間:2008年03月15日 18:15:11   作者:  
前言我寫Delphi程序是從MIS系統(tǒng)入門的,開始嘗試子系統(tǒng)劃分的時候采用的是MDI窗體的結(jié)構(gòu)。隨著系統(tǒng)功能的擴(kuò)充,不斷有新的子系統(tǒng)加入系統(tǒng)中,單個工程會變得非常大,每次做一點修改都要重新編譯,單個工程的形式也不利于團(tuán)隊協(xié)作。為了提高工作效率,我希望利用DLL動態(tài)鏈接庫的形式實現(xiàn)插件結(jié)構(gòu)的編程。
插件結(jié)構(gòu)的編程需要一個插件容器來控制各DLL的運行情況,將劃分好的每個子系統(tǒng)安排到一個DLL庫文件中。對每個DLL程序需要為容器預(yù)留接口函數(shù),一般接口函數(shù)包括:啟動調(diào)用DLL庫的函數(shù)、關(guān)閉DLL庫的函數(shù)。通過接口函數(shù),插件容器可以向DLL模塊傳遞參數(shù)實現(xiàn)動態(tài)控制。具體實現(xiàn)細(xì)節(jié)我將在下文說明并給出響應(yīng)代碼。

  您可能需要先了解一下DELPHI中UNIT的結(jié)構(gòu),工程的結(jié)構(gòu)。本文沒有深入討論DLL編程的理論細(xì)節(jié),只是演示了一些實用的代碼,我當(dāng)時學(xué)習(xí)的是劉藝?yán)蠋煹摹禗ELPHI深入編程》一書。

  我也處于DELPHI的入門階段,只是覺得這次的DLL開發(fā)有一些值得討論的地方,所以寫這篇文章,希望各位能對我做的不好的地方慷慨建議。

  示例程序簡介

  為了便于閱讀我將使用一個MIS系統(tǒng)的部分程序代碼演示插件編程的一些方法。示例程序是典型的C/S結(jié)構(gòu)DBMS應(yīng)用程序,我們關(guān)注的部分將是框架程序(下文簡稱Hall)的控制語句和dll插件程序的響應(yīng)控制。

  1、程序結(jié)構(gòu)

  插件容器Hall使用一個獨立的工程創(chuàng)建,Hall的主窗口的作用相當(dāng)于MDI程序中的MDI容器窗體,Hall中將顯式調(diào)用Dll中的接口函數(shù)。
每個插件程序獨立使用各自的工程,與普通工程不同的是,DLL工程創(chuàng)建的是Dll Wizard,相應(yīng)編譯生成的文件是以DLL為后綴。






  2、接口設(shè)計

  實例程序Narcissus中我們預(yù)留兩個接口函數(shù):

  ShowDLLForm

  該函數(shù)將應(yīng)用程序的句柄傳遞給DLL子窗口,DLL程序?qū)討B(tài)創(chuàng)建DLL窗體的實例。還可以將一些業(yè)務(wù)邏輯用參數(shù)的形式傳遞給DLL子窗口,比如窗體名稱、當(dāng)前登陸的用戶名等。初次調(diào)用一個DLL窗體實例時使用此函數(shù)創(chuàng)建。

  FreeDLLForm

  該函數(shù)將顯示釋放DLL窗口實例,在退出應(yīng)用程序時調(diào)用每個DLL窗體的FreeDLLForm方法來釋放創(chuàng)建的實例,不然會引起內(nèi)存只讀錯誤。同樣,也可以將一些在釋放窗體時需要做的業(yè)務(wù)邏輯用參數(shù)的形式傳遞給DLL窗體。

  3、調(diào)試方式

  DLL窗體程序無法直接執(zhí)行,需要有一個插件容器來調(diào)用。應(yīng)此我們需要先實現(xiàn)一個基本的Hall程序,然后將Hall.exe保存在一個固定的目錄中。對每個DLL工程做如下設(shè)置:

  1) 打開DLL工程

  2) 選擇菜單 Run – Parameters

  3) 在彈出的窗口中瀏覽到我們的容器Hall.exe

  這樣在調(diào)試DLL程序時將會自動調(diào)用Hall程序,利用Hall中預(yù)留的調(diào)用接口調(diào)試DLL程序。

 插件程序的基本實現(xiàn)

  DLL程序的設(shè)計方式和普通WINAPP沒有很大的區(qū)別,只是所有的窗口都是作為一種特殊的“資源”保存在DLL庫中,需要手動調(diào)用,而不像WINAPP中會有工程自動創(chuàng)建。聲明接口函數(shù)的方法很簡單

  1) 在Unit的Implementation部分中聲明函數(shù)

  2) 在函數(shù)聲明語句的尾部加上stdcall標(biāo)記

  3) 在工程代碼(Project – View Source)的begin語句之前,用exports語句聲明函數(shù)接口

  為了使代碼簡潔,我個人喜歡在工程中獨立添加一個Unit單元(File – New -- Unit),然后將所有要輸出的函數(shù)體定義在此單元中,不要忘記將引用到的窗體的Unit也uses進(jìn)來。我命名這個單元為UnitEntrance,在ShowDLLForm函數(shù)中初始化了要顯示的窗口并調(diào)用Show方法顯示,HALL會將登陸的用戶名用參數(shù)傳遞過來,得到用戶名后就可以進(jìn)行一些權(quán)限控制,表現(xiàn)在界面初始化上。

  其代碼如下

unit UnitOfficeEntrance;

interface
uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms;
 function ShowDLLForm(AHandle: THandle; ACaption: string; AUserID: string):boolean;stdcall;
 function FreeDLLForm(AHandle: THandle; ACaption: string; AUserID: string):boolean;stdcall;
implementation
 uses UnitOfficialMainForm; // 改成MAINFORM的unit
var
 DLL_Form:TFormOfficialMain; //改成MAINFORM的NAME

 //-----------------------------------------
 //Name: ShowDLLForm
 //Func: DLL插件調(diào)用入口函數(shù)
 //Para: AHandle 掛靠程序句柄; ACaption 本窗體標(biāo)題
 //Rtrn: N/A
 //Auth: CST
 //Date: 2005-6-3
 //-----------------------------------------

 function ShowDLLForm(AHandle: THandle; ACaption: string; AUserID: string):boolean;
 begin
  result:=true;
 try
  Application.Handle:=AHandle; //掛靠到主程序容器
  DLL_Form:=TFormOfficialMain.Create(Application); //改成MAINFORM的NAME
 try
  with DLL_Form do
  begin
   Caption := ACaption;
   StatusBar.Panels.Items[0].Text := AUserID;
   //Configure UI
   Show ;
  end;
 except
  on e:exception do
  begin
   dll_form.Free;
  end;
 end;
 except
  result:=false;
  end;
 end;

//-----------------------------------------
//Name: FreeDLLForm
//Func: DLL插件調(diào)用出口函數(shù)
//Para: AHandle 掛靠程序句柄
//Rtrn: true/false
//Auth: CST
//Date: 2005-6-11
//-----------------------------------------

function FreeDLLForm(AHandle: THandle; ACaption: string; AUserID: string):boolean;
begin
 Application.Handle:=AHandle; //掛靠到主程序容器
 if DLL_Form.Showing then DLL_Form.Close; //如果窗口打開先關(guān)閉,觸發(fā)FORM.CLOSEQUERY可取消關(guān)閉過程
 if not DLL_Form.Showing then
 begin
  DLL_Form.Free;
  result:=true;
 end //仍然打開狀態(tài),說明CLOSEQUERY.CANCLOSE=FALSE
 else
 begin
  result:=false;
 end;
end;
end.

  DLL工程文件代碼如下:

library Official;


{ Important note about DLL memory management: ShareMem must be the

first unit in your library's USES clause AND your project's (select

Project-View Source) USES clause if your DLL exports any procedures or

functions that pass strings as parameters or function results. This

applies to all strings passed to and from your DLL--even those that

are nested in records and classes. ShareMem is the interface unit to

the BORLNDMM.DLL shared memory manager, which must be deployed along

with your DLL. To avoid using BORLNDMM.DLL, pass string information

using PChar or ShortString parameters. }


uses

SysUtils,

Classes,

UnitOfficialDetailForm in 'UnitOfficialDetailForm.pas' {FormOfficialDetail},

UnitOfficialMainForm in 'UnitOfficialMainForm.pas' {FormOfficialMain},

UnitOfficeEntrance in 'UnitOfficeEntrance.pas',

UnitOfficialClass in '..\..\Public\Library\UnitOfficialClass.pas',

UnitMyDataAdatper in '..\..\Public\Library\UnitMyDataAdatper.pas',

UnitMyHeaders in '..\..\Public\Library\UnitMyHeaders.pas';


{$R *.res}

exports ShowDLLForm,FreeDLLForm; //接口函數(shù)

begin

end.

  插件程序一旦調(diào)用了DLL窗口,窗口實例將會保持在HALL窗口的上層,因此不用擔(dān)心遮擋的問題。

 容器程序的實現(xiàn)

  1、接口函數(shù)的引入

  調(diào)用DLL庫中的函數(shù)有顯式和隱式兩種方式,顯式調(diào)用更靈活,因此我們使用顯示調(diào)用。在Delphi中需要為接口函數(shù)申明函數(shù)類型,然后實例化函數(shù)類型的實例,該實例實際是一個指向函數(shù)的指針,通過指針我們可以訪問到函數(shù)并傳遞參數(shù)、獲取返回值。在單元文件的Interface部分加入函數(shù)類的申明:

type

//定義接口函數(shù)類型,接口函數(shù)來自DLL接口

TShowDLLForm = Function(AHandle:THandle; ACaption: String; AUserID:string):Boolean;stdcall;

TFreeDLLForm = Function(AHandle:THandle; ACaption: String; AUserID:string):boolean;stdcall;

  顯示調(diào)用庫函數(shù)需要如下幾個步驟:

  1) 載入DLL庫文件

  2) 獲得函數(shù)地址

  3) 執(zhí)行函數(shù)

  4) 釋放DLL庫

  接下來我們將詳細(xì)討論這幾個步驟。

  2、載入DLL庫文件

  通過調(diào)用API函數(shù)LoadLibrary可以將DLL庫載入到內(nèi)存中,在此我們不討論DLL對內(nèi)存管理的影響。LoadLibrary的參數(shù)是DLL文件的地址路徑,如果載入成功會返回一個CARDINAL類型的變量作為DLL庫的句柄;如果目標(biāo)文件不存在或其他原因?qū)е螺d入DLL文件失敗會返回一個0。

  3、實例化接口函數(shù)

  獲得接口函數(shù)指針的API函數(shù)為GetProcAddress(庫文件句柄,函數(shù)名稱),如果找到函數(shù)則會返回該函數(shù)的指針,如果失敗則返回NIL。
使用上文定義的函數(shù)類型定義函數(shù)指針變量,然后使用@操作符獲得函數(shù)地址,這樣就可以使用指針變量訪問函數(shù)。主要代碼如下:

……
var
 ShowDLLForm: TShowDLLForm; //DLL接口函數(shù)實例
 FreeDLLForm: TFreeDLLForm;
begin
 try
 begin
  APlugin.ProcAddr := LoadLibrary(PChar(sPath));
  APlugin.FuncFreeAddr := GetProcAddress(APlugin.ProcAddr,'FreeDLLForm');
  APlugin.FuncAddr := GetProcAddress(APlugin.ProcAddr ,'ShowDLLForm');

  @ShowDLLForm:=APlugin.FuncAddr ;
  @FreeDLLForm:=APlugin.FuncFreeAddr;
  if ShowDllForm(Self.Handle, APlugin.Caption , APlugin.UserID) then
   Result:=True
   ……

  4、一個具體的實現(xiàn)方法

  為了結(jié)構(gòu)化管理插件,方便今后的系統(tǒng)擴(kuò)充,我們可以結(jié)合數(shù)據(jù)庫記錄可用的DLL信息,然后通過查詢數(shù)據(jù)庫記錄動態(tài)訪問DLL程序。

  1) 系統(tǒng)模塊表設(shè)計

  對于MIS系統(tǒng),可以利用已有的DBS條件建立一個系統(tǒng)模塊表,記錄DLL文件及映射到系統(tǒng)模塊中的相關(guān)信息

字段名 作用 類型
AutoID 索引 INT
modAlias 模塊別稱 VARCHAR
modName 模塊名稱 VARCHAR
modWndClass 窗體唯一標(biāo)識 VARCHAR
modFile DLL路徑 VARCHAR
modMemo 備注 TEXT

  ·模塊別稱是用來在編程設(shè)計階段統(tǒng)一命名的規(guī)則,特別是團(tuán)隊開發(fā)時可以供隊員參考。

  ·模塊名稱將作為ACAPTION參數(shù)傳遞給SHOWDLLFORM函數(shù)作為DLL窗口的標(biāo)題。

  ·窗體唯一標(biāo)識是DLL子模塊中主窗口的CLASSNAME,用來在運行時確定要控制的窗口。

  ·DLL路徑保存DLL文件名稱,程序中將轉(zhuǎn)換為絕對路徑。

  2) 插件信息數(shù)據(jù)結(jié)構(gòu)

  定義一個記錄插件相關(guān)信息的數(shù)據(jù)接口可以集中控制DLL插件。在Interface部分加入如下代碼:

type

 //定義插件信息類

 TMyPlugins = class
 Caption:String; //DLL窗體標(biāo)題
 DllFileName:String; //DLL文件路徑
 WndClass:String; //窗體標(biāo)識
 UserID:string; //用戶名
 ProcAddr:THandle; //LOADLIBRARY載入的庫句柄
 FuncAddr:Pointer; //SHOWDLLFORM函數(shù)指針
 FuncFreeAddr:Pointer; //FREEDLLFORM函數(shù)指針
end;

……

  為每個插件創(chuàng)建一個TMyPlugins的實例,下文會討論對這些實例的初始化方法。

  3) 插件載入函數(shù)

  在本示例中DLL窗口是在HALL中觸發(fā)打開子窗口的事件中載入并顯示的。按鈕事件觸發(fā)后,先根據(jù)插件結(jié)構(gòu)體實例判斷DLL是否已經(jīng)加載,如果已經(jīng)加載,則控制窗口的顯示或關(guān)閉;如果沒有加載則訪問數(shù)據(jù)表將字段賦值到插件結(jié)構(gòu)體中,然后執(zhí)行載入、獲得指針的工作。

  局部代碼如下

……
//-----------------------------------------

//Name: OpenPlugin

//Func: 插件信息類控制過程: 初始化==》設(shè)置權(quán)限==》載入DLL窗口

//Para: APlugin-TMyPlugins; sAlias別名; iFuncValue權(quán)限值

//Rtrn: N/A

//Auth: CST

//Date: 2005-6-2

//-----------------------------------------

procedure TFormHall.OpenPlugin(AFromActn: TAction ;APlugin:TMyPlugins; sAlias:string; sUserID:string);
 var hWndPlugin:HWnd;
begin
 
 //判斷插件窗口是否已經(jīng)載入 hWndPlugin:=FindWindow(PChar(APlugin.WndClass),nil);
 if hWndPlugin <> 0 then //插件窗口已經(jīng)載入
 begin
  if not IsWindowVisible(hWndPlugin) then
  begin
   AFromActn.Checked := True;
   ShowWindow(hWndPlugin,SW_SHOWDEFAULT); //顯示
  end
  else
  begin
   AFromActn.checked := False;
   ShowWindow(hWndPlugin,SW_HIDE) ;
  end;
  Exit; //離開創(chuàng)建插件過程
 end;

//初始化插件類實例

if not InitializeMyPlugins(APlugin,sAlias) then
begin
 showmessage('初始化插件類錯誤。');
 exit;
end;

//獲得當(dāng)前權(quán)限值

APlugin.UserID := sUserID;
//載入DLL窗口

if not LoadShowPluginForm(APlugin) then
begin
 showmessage('載入中心插件出錯。');
 exit;
 end;
end;

//-----------------------------------------
//Name: InitializeMyPlugins
//Func: 初始化MYPLUGIN實例 (Caption | DllFileName | IsLoaded)
//Para: APlugin-TMyPlugins
//Rtrn: N/A
//Auth: CST
//Date: 2005-6-2
//-----------------------------------------

function TFormHall.InitializeMyPlugins(APlugin:TMyPlugins; sAlias:String):Boolean;
var
 strSQL:string;
 myDA:TMyDataAdapter;
begin
 Result:=False;
 myDA:=TMyDataAdapter.Create;
 strSQL:='SELECT * FROM SystemModuleList WHERE modAlias='+QuotedStr(sAlias);
 try
  myDA.RetrieveData(strSQL);
 except
  on E:Exception do
  begin
   result:=false;
   myDA.Free ;
   exit;
  end;
 end;
try
 begin
  with myDA.MyDataSet do
 begin
  if Not IsEmpty then
 begin
  APlugin.Caption:= FieldByName('modName').Value;
  APlugin.DllFileName := FieldByName('modFile').Value;
  APlugin.WndClass := FieldByName('modWndClass').Value ;
  result:=True;
 end;
Close;
 end; //end of with...do...
 end; //end of try
 except
  on E:Exception do
begin
 Result:=False;
 myDA.Free ;
 Exit;
 end; //end of exception
end; //end of try...except

 myDA.Free ;
end;



//-----------------------------------------

//Name: LoadShowPluginForm

//Func: 載入DLL插件并顯示窗口

//Para: APlugin-TMyPlugins

//Rtrn: true-創(chuàng)建成功

//Auth: CST

//Date: 2005-6-2

//-----------------------------------------

function TFormHall.LoadShowPluginForm (const APlugin:TMyPlugins):boolean;

var
 ShowDLLForm: TShowDLLForm; //DLL接口函數(shù)實例
 FreeDLLForm: TFreeDLLForm;
 sPath:string; //DLL文件的完整路徑
begin
 try
 begin
  sPath:=ExtractFilepath(Application.ExeName)+ 'plugins\' + APlugin.DllFileName ;
  APlugin.ProcAddr := LoadLibrary(PChar(sPath));
  APlugin.FuncFreeAddr := GetProcAddress(APlugin.ProcAddr,'FreeDLLForm');
  APlugin.FuncAddr := GetProcAddress(APlugin.ProcAddr ,'ShowDLLForm');
  @ShowDLLForm:=APlugin.FuncAddr ;
  @FreeDLLForm:=APlugin.FuncFreeAddr;
  if ShowDllForm(Self.Handle, APlugin.Caption , APlugin.UserID) then
   Result:=True
  else
   Result:=False;
  end;
  except
   on E:Exception do
  begin
   Result:=False;
   ShowMessage('載入插件模塊錯誤,請檢查PLUGINS目錄里的文件是否完整。');
  end;
 end;
end;

……

  4) DLL窗口控制

  正如3)中的代碼說明的那樣,DLL窗口的打開和關(guān)閉只是在表象層,關(guān)閉窗口并沒有真正釋放DLL窗口,只是調(diào)用API函數(shù)FindWindow根據(jù)窗口標(biāo)識(就是Form.name)獲得窗體句柄,用SHOWWINDOW函數(shù)的nCmdShow參數(shù)控制窗口顯示/隱藏。

  其實這是我這個程序?qū)崿F(xiàn)的不好的一個地方,如果在DLL窗口中使用Self.close方法會引起內(nèi)存錯誤,實在能力有限沒有辦法解決,因此出此下策。所以每個DLL程序主窗口的關(guān)閉按鈕都必須隱藏掉。 :-P

  5) DLL庫的釋放

  在程序退出時,必須根據(jù)插件信息實例逐一釋放DLL庫。釋放DLL庫的函數(shù)如下:

procedure TFormHall.ClosePlugin(aPLG:TMyPlugins);
var
 FreeDLLForm:TFreeDLLForm;
begin
 if aPLG.ProcAddr = 0 then exit;
 if aPLG.FuncFreeAddr = nil then exit;
 @FreeDLLForm:=aPLG.FuncFreeAddr;
 if not FreeDLLForm(Application.Handle,'','') then
  showMessage('err');
end;

  小結(jié)

  本實例程序運行效果如下:






  我以上的方法中,因為有不少能力有限沒有解決的問題,所以采用了一些看起來不太合理的掩飾方法,希望大家能在做了一點嘗試后設(shè)計出更好的解決方法,我也希望能學(xué)到更多的好方法。

相關(guān)文章

最新評論