| 導購 | 订阅 | 在线投稿
分享
 
 
 

初探Delphi 7 中的插件編程

來源:互聯網網民  2006-01-31 22:30:03  評論

初探Delphi 7 中的插件編程

caishaoting 2005-07-10

1 前言

1.1 寫作目的

我寫Delphi程序是從MIS系統入門的,開始嘗試子系統劃分的時候采用的是MDI窗體的結構。隨著系統功能的擴充,不斷有新的子系統加入系統中,單個工程會變得非常大,每次做一點修改都要重新編譯,單個工程的形式也不利于團隊協作。爲了提高工作效率,我希望利用DLL動態鏈接庫的形式實現插件結構的編程。

插件結構的編程需要一個插件容器來控制各DLL的運行情況,將劃分好的每個子系統安排到一個DLL庫文件中。對每個DLL程序需要爲容器預留接口函數,一般接口函數包括:啓動調用DLL庫的函數、關閉DLL庫的函數。通過接口函數,插件容器可以向DLL模塊傳遞參數實現動態控制。具體實現細節我將在下文說明並給出響應代碼。

1.2 閱讀對象

您可能需要先了解一下DELPHI中UNIT的結構,工程的結構。本文沒有深入討論DLL編程的理論細節,只是演示了一些實用的代碼,我當時學習的是劉藝

老師的《DELPHI深入編程》一書。

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

2 示例程序簡介

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

2.1 程序結構

插件容器Hall使用一個獨立的工程創建,Hall的主窗口的作用相當于MDI程序中的MDI容器窗體,Hall中將顯式調用Dll中的接口函數。

每個插件程序獨立使用各自的工程,與普通工程不同的是,DLL工程創建的是Dll Wizard,相應編譯生成的文件是以DLL爲後綴。

初探Delphi 7 中的插件編程

2.2 接口設計

實例程序Narcissus中我們預留兩個接口函數:

ShowDLLForm

該函數將應用程序的句柄傳遞給DLL子窗口,DLL程序將動態創建DLL窗體的實例。還可以將一些業務邏輯用參數的形式傳遞給DLL子窗口,比如窗體名稱、當前登陸的用戶名等。初次調用一個DLL窗體實例時使用此函數創建。

FreeDLLForm

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

2.3 調試方式

DLL窗體程序無法直接執行,需要有一個插件容器來調用。應此我們需要先實現一個基本的Hall程序,然後將Hall.exe保存在一個固定的目錄中。對每個DLL工程做如下設置:

1. 打開DLL工程

2. 選擇菜單 Run – Parameters

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

這樣在調試DLL程序時將會自動調用Hall程序,利用Hall中預留的調用接口調試DLL程序。

3 插件程序的基本實現

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

1. 在Unit的Implementation部分中聲明函數

2. 在函數聲明語句的尾部加上stdcall標記

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

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

其代碼如下

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插件調用入口函數

//Para: AHandle 挂靠程序句柄; ACaption 本窗體標題

//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插件調用出口函數

//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; //如果窗口打開先關閉,觸發FORM.CLOSEQUERY可取消關閉過程

if not DLL_Form.Showing then

begin

DLL_Form.Free;

result:=true;

end //仍然打開狀態,說明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; //接口函數

begin

end.

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

4 容器程序的實現。

4.1 接口函數的引入

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

type

//定義接口函數類型,接口函數來自DLL接口

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

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

顯示調用庫函數需要如下幾個步驟

1. 載入DLL庫文件

2. 獲得函數地址

3. 執行函數

4. 釋放DLL庫

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

4.2 載入DLL庫文件

通過調用API函數LoadLibrary可以將DLL庫載入到內存中,在此我們不討論DLL對內存管理的影響。LoadLibrary的參數是DLL文件的地址路徑,如果載入成功會返回一個CARDINAL類型的變量作爲DLL庫的句柄;如果目標文件不存在或其他原因導致載入DLL文件失敗會返回一個0。

4.3 實例化接口函數

獲得接口函數指針的API函數爲GetProcAddress(庫文件句柄,函數名稱),如果找到函數則會返回該函數的指針,如果失敗則返回NIL。

使用上文定義的函數類型定義函數指針變量,然後使用@操作符獲得函數地址,這樣就可以使用指針變量訪問函數。主要代碼如下:

……

var

ShowDLLForm: TShowDLLForm; //DLL接口函數實例

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.4 一個具體的實現方法

爲了結構化管理插件,方便今後的系統擴充,我們可以結合數據庫記錄可用的DLL信息,然後通過查詢數據庫記錄動態訪問DLL程序。

4.4.1 系統模塊表設計

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

字段名

作用

類型

AutoID

索引

INT

modAlias

模塊別稱

VARCHAR

modName

模塊名稱

VARCHAR

modWndClass

窗體唯一標識

VARCHAR

modFile

DLL路徑

VARCHAR

modMemo

備注

TEXT

n 模塊別稱是用來在編程設計階段統一命名的規則,特別是團隊開發時可以供隊員參考。

n 模塊名稱將作爲ACAPTION參數傳遞給SHOWDLLFORM函數作爲DLL窗口的標題。

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

n DLL路徑保存DLL文件名稱,程序中將轉換爲絕對路徑。

4.4.2 插件信息數據結構

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

type

//定義插件信息類

TMyPlugins = class

Caption:String; //DLL窗體標題

DllFileName:String; //DLL文件路徑

WndClass:String; //窗體標識

UserID:string; //用戶名

ProcAddr:THandle; //LOADLIBRARY載入的庫句柄

FuncAddr:Pointer; //SHOWDLLFORM函數指針

FuncFreeAddr:Pointer; //FREEDLLFORM函數指針

end;

……

爲每個插件創建一個TMyPlugins的實例,下文會討論對這些實例的初始化方法。

4.4.3 插件載入函數

在本示例中DLL窗口是在HALL中觸發打開子窗口的事件中載入並顯示的。按鈕事件觸發後,先根據插件結構體實例判斷DLL是否已經加載,如果已經加載,則控制窗口的顯示或關閉;如果沒有加載則訪問數據表將字段賦值到插件結構體中,然後執行載入、獲得指針的工作。

局部代碼如下

……

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

//Name: OpenPlugin

//Func: 插件信息類控制過程: 初始化==》設置權限==》載入DLL窗口

//Para: APlugin-TMyPlugins; sAlias別名; iFuncValue權限值

//Rtrn: N/A

//Auth: CST

//Date: 2005-6-2

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

procedure TFormHall.OpenPlugin(AFromActn: TAction ;APlugin:TMyPlugins; sAlias:string; sUserID:string);

var hWndPlugin:HWnd;

begin

//判斷插件窗口是否已經載入

hWndPlugin:=FindWindow(PChar(APlugin.WndClass),nil);

if hWndPlugin <> 0 then //插件窗口已經載入

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; //離開創建插件過程

end;

//初始化插件類實例

if not InitializeMyPlugins(APlugin,sAlias) then

begin

showmessage('初始化插件類錯誤。');

exit;

end;

//獲得當前權限值

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-創建成功

//Auth: CST

//Date: 2005-6-2

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

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

var

ShowDLLForm: TShowDLLForm; //DLL接口函數實例

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.4.4 DLL窗口控制

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

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

4.4.5 DLL庫的釋放

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

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;

5 小結

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

初探Delphi 7 中的插件編程

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

本文只給出了部分代碼,如果您需要完整的代碼可以寫郵件給我。我也很歡迎各位能對我的方法提出建議和意見,這是我第一次嘗試DLL編程的一點感受,希望能給同樣也有興趣的朋友一點線索。

 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
初探Delphi 7 中的插件編程 caishaoting 2005-07-10 1 前言 1.1 寫作目的 我寫Delphi程序是從MIS系統入門的,開始嘗試子系統劃分的時候采用的是MDI窗體的結構。隨著系統功能的擴充,不斷有新的子系統加入系統中,單個工程會變得非常大,每次做一點修改都要重新編譯,單個工程的形式也不利于團隊協作。爲了提高工作效率,我希望利用DLL動態鏈接庫的形式實現插件結構的編程。 插件結構的編程需要一個插件容器來控制各DLL的運行情況,將劃分好的每個子系統安排到一個DLL庫文件中。對每個DLL程序需要爲容器預留接口函數,一般接口函數包括:啓動調用DLL庫的函數、關閉DLL庫的函數。通過接口函數,插件容器可以向DLL模塊傳遞參數實現動態控制。具體實現細節我將在下文說明並給出響應代碼。 1.2 閱讀對象 您可能需要先了解一下DELPHI中UNIT的結構,工程的結構。本文沒有深入討論DLL編程的理論細節,只是演示了一些實用的代碼,我當時學習的是劉藝 老師的《DELPHI深入編程》一書。 我也處于DELPHI的入門階段,只是覺得這次的DLL開發有一些值得討論的地方,所以寫這篇文章,希望各位能對我做的不好的地方慷慨建議。 2 示例程序簡介 爲了便于閱讀我將使用一個MIS系統的部分程序代碼演示插件編程的一些方法。示例程序是典型的C/S結構DBMS應用程序,我們關注的部分將是框架程序(下文簡稱Hall)的控制語句和dll插件程序的響應控制。 2.1 程序結構 插件容器Hall使用一個獨立的工程創建,Hall的主窗口的作用相當于MDI程序中的MDI容器窗體,Hall中將顯式調用Dll中的接口函數。 每個插件程序獨立使用各自的工程,與普通工程不同的是,DLL工程創建的是Dll Wizard,相應編譯生成的文件是以DLL爲後綴。 [url=/bbs/detail_148994.html][img]http://dev.csdn.net/images/blog_csdn_net/mrtechno/image001.JPG[/img][/url] 2.2 接口設計 實例程序Narcissus中我們預留兩個接口函數: ShowDLLForm 該函數將應用程序的句柄傳遞給DLL子窗口,DLL程序將動態創建DLL窗體的實例。還可以將一些業務邏輯用參數的形式傳遞給DLL子窗口,比如窗體名稱、當前登陸的用戶名等。初次調用一個DLL窗體實例時使用此函數創建。 FreeDLLForm 該函數將顯示釋放DLL窗口實例,在退出應用程序時調用每個DLL窗體的FreeDLLForm方法來釋放創建的實例,不然會引起內存只讀錯誤。同樣,也可以將一些在釋放窗體時需要做的業務邏輯用參數的形式傳遞給DLL窗體。 2.3 調試方式 DLL窗體程序無法直接執行,需要有一個插件容器來調用。應此我們需要先實現一個基本的Hall程序,然後將Hall.exe保存在一個固定的目錄中。對每個DLL工程做如下設置: 1. 打開DLL工程 2. 選擇菜單 Run – Parameters 3. 在彈出的窗口中浏覽到我們的容器Hall.exe 這樣在調試DLL程序時將會自動調用Hall程序,利用Hall中預留的調用接口調試DLL程序。 3 插件程序的基本實現 DLL程序的設計方式和普通WINAPP沒有很大的區別,只是所有的窗口都是作爲一種特殊的“資源”保存在DLL庫中,需要手動調用,而不像WINAPP中會有工程自動創建。聲明接口函數的方法很簡單 1. 在Unit的Implementation部分中聲明函數 2. 在函數聲明語句的尾部加上stdcall標記 3. 在工程代碼(Project – View Source)的begin語句之前,用exports語句聲明函數接口 爲了使代碼簡潔,我個人喜歡在工程中獨立添加一個Unit單元(File – New -- Unit),然後將所有要輸出的函數體定義在此單元中,不要忘記將引用到的窗體的Unit也uses進來。我命名這個單元爲UnitEntrance,在ShowDLLForm函數中初始化了要顯示的窗口並調用Show方法顯示,HALL會將登陸的用戶名用參數傳遞過來,得到用戶名後就可以進行一些權限控制,表現在界面初始化上。 其代碼如下 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插件調用入口函數 //Para: AHandle 挂靠程序句柄; ACaption 本窗體標題 //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插件調用出口函數 //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; //如果窗口打開先關閉,觸發FORM.CLOSEQUERY可取消關閉過程 if not DLL_Form.Showing then begin DLL_Form.Free; result:=true; end //仍然打開狀態,說明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; //接口函數 begin end. 插件程序一旦調用了DLL窗口,窗口實例將會保持在HALL窗口的上層,因此不用擔心遮擋的問題。 4 容器程序的實現。 4.1 接口函數的引入 調用DLL庫中的函數有顯式和隱式兩種方式,顯式調用更靈活,因此我們使用顯示調用。在Delphi中需要爲接口函數申明函數類型,然後實例化函數類型的實例,該實例實際是一個指向函數的指針,通過指針我們可以訪問到函數並傳遞參數、獲取返回值。在單元文件的Interface部分加入函數類的申明: type //定義接口函數類型,接口函數來自DLL接口 TShowDLLForm = Function(AHandle:THandle; ACaption: String; AUserID:string):Boolean;stdcall; TFreeDLLForm = Function(AHandle:THandle; ACaption: String; AUserID:string):boolean;stdcall; 顯示調用庫函數需要如下幾個步驟 1. 載入DLL庫文件 2. 獲得函數地址 3. 執行函數 4. 釋放DLL庫 接下來我們將詳細討論這幾個步驟。 4.2 載入DLL庫文件 通過調用API函數LoadLibrary可以將DLL庫載入到內存中,在此我們不討論DLL對內存管理的影響。LoadLibrary的參數是DLL文件的地址路徑,如果載入成功會返回一個CARDINAL類型的變量作爲DLL庫的句柄;如果目標文件不存在或其他原因導致載入DLL文件失敗會返回一個0。 4.3 實例化接口函數 獲得接口函數指針的API函數爲GetProcAddress(庫文件句柄,函數名稱),如果找到函數則會返回該函數的指針,如果失敗則返回NIL。 使用上文定義的函數類型定義函數指針變量,然後使用@操作符獲得函數地址,這樣就可以使用指針變量訪問函數。主要代碼如下: …… var ShowDLLForm: TShowDLLForm; //DLL接口函數實例 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.4 一個具體的實現方法 爲了結構化管理插件,方便今後的系統擴充,我們可以結合數據庫記錄可用的DLL信息,然後通過查詢數據庫記錄動態訪問DLL程序。 4.4.1 系統模塊表設計 對于MIS系統,可以利用已有的DBS條件建立一個系統模塊表,記錄DLL文件及映射到系統模塊中的相關信息 字段名 作用 類型 AutoID 索引 INT modAlias 模塊別稱 VARCHAR modName 模塊名稱 VARCHAR modWndClass 窗體唯一標識 VARCHAR modFile DLL路徑 VARCHAR modMemo 備注 TEXT n 模塊別稱是用來在編程設計階段統一命名的規則,特別是團隊開發時可以供隊員參考。 n 模塊名稱將作爲ACAPTION參數傳遞給SHOWDLLFORM函數作爲DLL窗口的標題。 n 窗體唯一標識是DLL子模塊中主窗口的CLASSNAME,用來在運行時確定要控制的窗口。 n DLL路徑保存DLL文件名稱,程序中將轉換爲絕對路徑。 4.4.2 插件信息數據結構 定義一個記錄插件相關信息的數據接口可以集中控制DLL插件。在Interface部分加入如下代碼: type //定義插件信息類 TMyPlugins = class Caption:String; //DLL窗體標題 DllFileName:String; //DLL文件路徑 WndClass:String; //窗體標識 UserID:string; //用戶名 ProcAddr:THandle; //LOADLIBRARY載入的庫句柄 FuncAddr:Pointer; //SHOWDLLFORM函數指針 FuncFreeAddr:Pointer; //FREEDLLFORM函數指針 end; …… 爲每個插件創建一個TMyPlugins的實例,下文會討論對這些實例的初始化方法。 4.4.3 插件載入函數 在本示例中DLL窗口是在HALL中觸發打開子窗口的事件中載入並顯示的。按鈕事件觸發後,先根據插件結構體實例判斷DLL是否已經加載,如果已經加載,則控制窗口的顯示或關閉;如果沒有加載則訪問數據表將字段賦值到插件結構體中,然後執行載入、獲得指針的工作。 局部代碼如下 …… //----------------------------------------- //Name: OpenPlugin //Func: 插件信息類控制過程: 初始化==》設置權限==》載入DLL窗口 //Para: APlugin-TMyPlugins; sAlias別名; iFuncValue權限值 //Rtrn: N/A //Auth: CST //Date: 2005-6-2 //----------------------------------------- procedure TFormHall.OpenPlugin(AFromActn: TAction ;APlugin:TMyPlugins; sAlias:string; sUserID:string); var hWndPlugin:HWnd; begin //判斷插件窗口是否已經載入 hWndPlugin:=FindWindow(PChar(APlugin.WndClass),nil); if hWndPlugin <> 0 then //插件窗口已經載入 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; //離開創建插件過程 end; //初始化插件類實例 if not InitializeMyPlugins(APlugin,sAlias) then begin showmessage('初始化插件類錯誤。'); exit; end; //獲得當前權限值 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-創建成功 //Auth: CST //Date: 2005-6-2 //----------------------------------------- function TFormHall.LoadShowPluginForm (const APlugin:TMyPlugins):boolean; var ShowDLLForm: TShowDLLForm; //DLL接口函數實例 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.4.4 DLL窗口控制 正如4.4.3中的代碼說明的那樣,DLL窗口的打開和關閉只是在表象層,關閉窗口並沒有真正釋放DLL窗口,只是調用API函數FindWindow根據窗口標識(就是Form.name)獲得窗體句柄,用SHOWWINDOW函數的nCmdShow參數控制窗口顯示/隱藏。 其實這是我這個程序實現的不好的一個地方,如果在DLL窗口中使用Self.close方法會引起內存錯誤,實在能力有限沒有辦法解決,因此出此下策。所以每個DLL程序主窗口的關閉按鈕都必須隱藏掉。 :-P 4.4.5 DLL庫的釋放 在程序退出時,必須根據插件信息實例逐一釋放DLL庫。釋放DLL庫的函數如下: 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; 5 小結 本實例程序運行效果如下: [url=/bbs/detail_148994.html][img]http://dev.csdn.net/images/blog_csdn_net/mrtechno/image008.jpg[/img][/url] 我以上的方法中,因爲有不少能力有限沒有解決的問題,所以采用了一些看起來不太合理的掩飾方法,希望大家能在做了一點嘗試後設計出更好的解決方法,我也希望能學到更多的好方法。 本文只給出了部分代碼,如果您需要完整的代碼可以寫郵件給我。我也很歡迎各位能對我的方法提出建議和意見,這是我第一次嘗試DLL編程的一點感受,希望能給同樣也有興趣的朋友一點線索。
󰈣󰈤
王朝萬家燈火計劃
期待原創作者加盟
 
 
 
>>返回首頁<<
 
 
 
 
 
 熱帖排行
 
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有