首頁 > 类库下载 > 其它類別庫 > 為什麼要使用 Delphi中的動態包

為什麼要使用 Delphi中的動態包

伊谢尔伦
發布: 2016-11-21 11:34:44
原創
2038 人瀏覽過

為什麼要使用包包?

答案很簡單:因為包的功能強大。設計期套件(design-time package)簡化了自訂元件的發布和安裝;而運行期套件(run-time package) 則更是為傳統的程式設計注入了新鮮的力量。一旦把可重複使用的程式碼編譯為運行期程式庫中,你就可以在多個應用程式中共用它們。所有應用程式都可以透過套件存取標準組 件,Delphi本身就是這麼幹的。因為應用程式不必在可執行檔中單獨複製一份元件庫,這樣就大 大節省了系統資源和磁碟空間。此外,套件還可以減少花費在編譯上的時間,因為你只需編譯應用程式特有的程式碼。

如果可以動態的使用包,那麼我們還可以獲得更多的好處。包提供了一種新穎的模組化方法來開發應用程式。有些時候你也 許想把某些模組當作應用程式的選用零件,例如一個記帳系統附帶一個可選的HR模組。某些情況下,你 只需安裝基本的應用程序,而在另外一些情況下你可能需要額外安裝HR模組。這種模組化的方法可以 透過包技術很容易的實現。在過去,這只能透過動態裝載DLL實現,但是使用Delphi的套件技術,你就可以把應用程式的各個模組類型分別打「包」成捆。特別是從套件中建立的類別物件則屬於應 用程式所有,因此可以與應用程式中的物件互動。

運行期套件與應用程式

許多開發者只把Delphi套件看作放組件的地方,事實上包可 以(而且也應該)應用於模組化應用程式設計。

為了示範如何用套件來模組化你的應用程序,我們建立一個例子:

1、 新建一個具有兩個窗體的Delphi程式:Form1和Form2;

2、 將Form2從自動建立窗體清單中移除(Project |Options | Forms);

3、 在Form1上放一個按鈕,並且在按鈕的OnClick事件處理器中輸入如下代碼:

with TForm2.Create(Application) do
begin
ShowModal;
Free;
End;
登入後複製

4、記住添加Unit2到Unit1的uses子句中;

5、 保存並運作工程。

我們創建了一個簡單的應用程序,它顯示一個帶有按鈕的窗體,點擊這個按鈕則會創建並顯示出另一個窗體。

但是如果想將上述範例中的Form2包含在一個可重複使用模組 中,並使它仍然可以正常工作,我們該怎麼辦呢?

答案是:包包!

要為Form2建立套件需要以下工作:

1、 開啟工程管理器(View | Project Manager);

2 、右鍵Project Group,選擇「Add NewProject...」;

3、在「「 New」項目清單中選擇「Package」;

4、 現在你應該可以見到包編輯器;

5、選擇「Contains」項目,然後點擊「Add」按鈕;

6、 然後點擊「Browse. ..」按鈕,並選擇「Unit2.pas」;

7、現在套件中應該包含了「Unit2.pas」單元;

8、 最後儲存並編譯套件。

現在我們完成了這個包。在你的ProjectBPL目錄中 應該會有一個名叫「package1.bpl」的檔案。 (BPL是Borland Package Library的縮寫,DCP是Delphi CompiledPackage 的縮寫。)

這個包已經完成了。現在我們需要打開套件選項開關

並重新編譯原先的應用程式。

1、 在工程管理器中雙擊「Project1.exe」以選取該工程;

2、 右鍵並選擇「Options...」(你也可以從選單中選擇Project | Options...);

3、 選取「Packages」選項頁;

4、 選取「Build with runtime packages」 檢查框;

5、 編輯「Runtime packages」編輯框:「Vcl50;Package1」,並點選「OK」按鈕」;

6、 注意:不要從應用程式移除Unit2;

7、 儲存並執行應用程式。

應用程式會像從前一樣運行,不過區別可以從檔案的大小上看出來。

Project1.exe現在只有14K大 小,而從前則是293K。如果你用資源瀏覽器查看EXE和BPL檔案的內容,你會發現Form2的DFM和程式碼現在都保存在套件中。

Delphi在編譯期完成對套件的靜態連線。 (這就是為什麼你不能從EXE工程中移除Unit2。)

想想你可以由此得到什麼:你可以在套件中創建一個資料存取模組,並且在更改資料存取規則時(例如從BDE連接轉為ADO連接),稍作修改並重新發布這個包。或者,你可以在某個套件中建立一個顯示「此選項在目前版本中不可用」資訊的窗體,然後在另一個同名的套件中建立一個具有完整功能的窗體。現在我們不費吹灰 之力就有了「Pro」和「Enterprise」 兩個版本的產品。

包的動態裝載和卸載

在大多数情况下,静态连接的DLL或BPL已经可以满足要求了。但是如果我们不想发布BPL呢? “在指定目录中找不到动态链接库Package1.bpl”,这是在应用程序终止前,我们所能得到 的唯一消息。或者,在模块化应用程序程序中,我们是否可以使用任意数量的插件?

我们需要在运行期动态连接到BPL。

对于DLL 来说,有一个简单的方法,就是使用LoadLibrary函数:

function LoadLibrary(lpLibFileName: Pchar): HMODULE;stdcall;

装载了DLL之后,我们可以使用GetProcAddress函数来调用DLL的导出函 数和方法:

function GetProcAddress(hModule: HMODULE; lpProcName:LPCSTR): FARPROC; stdcall;

最后,我们使用FreeLibrary卸载DLL:

function FreeLibrary(hLibModule: HMODULE): BOOL;stdcall;

下面这个例子中我们动态装载Microsoft的HtmlHelp库:

function TForm1.ApplicationEvents1Help(Command: Word; Data: Integer; var CallHelp: Boolean):Boolean;
type
TFNHtmlHelpA = function(hwndCaller: HWND; pszFile: PansiChar; uCommand: UINT;dwData: Dword): HWND; stdcall;
var
HelpModule: Hmodule;
HtmlHelp: TFNHtmlHelpA;
begin
Result := False;
HelpModule := LoadLibrary('HHCTRL.OCX');
if HelpModule <> 0 then
begin
@HtmlHelp := GetProcAddress(HelpModule, &#39;HtmlHelpA&#39;);
if @HtmlHelp <> nil then
Result := HtmlHelp(Application.Handle,Pchar(Application.HelpFile), Command,Data) <> 0;
FreeLibrary(HelpModule);
end;
CallHelp := False;
end;
登入後複製

动态装载BPL

我们可以用同样简单的方法来对付BPL,或者应该说基本上同 样简单。

我们可以使用LoadPackage函数动态装载包:

function LoadPackage(const Name: string): HMODULE;

然后使用GetClass 函数创建一个TPersistentClass类型对象:

function GetClass(const AclassName: string):TPersistentClass;

完成所有操作后,使用UnLoadPackage(Module:HModule);

让我们对原来的代码作一些小小的改动:

1、 在工程管理器中选中“Project1.exe”;

2、 右击之并选择“Options...”;

3、 选中“Packages”选项页;

4 、 从“Runtime packages”编辑框中移除“Package1”,并点击OK按钮;

5、 在Delphi的工具栏中,点击“Remove file from project”按钮;

6、 选择“Unit2 | Form2”,并点击OK;

7、 现在在“Unit1.pas”的源代码中,从uses子句中移除Unit2;

8、 进入Button1 的OnClick时间代码中;

9、 添加两个HModule和TPersistentClass类型的变量:

var

PackageModule: HModule;

AClass: TPersistentClass;

10、使用LoadPackage 函数装载Pacakge1包:

PackageModule := LoadPackage('Package1.bpl');

11、检查PackageModule是否为0;

12、使用GetClass函数创建一个持久类型:

AClass := GetClass('TForm2');

13、如果这个持久类型不为nil,我们就可以向从前

一样创建并使用该类型的对象了:

with TComponentClass(AClass).Create(Application) as TcustomForm do
begin
ShowModal;
Free;
end;
登入後複製

14、最后,使用UnloadPackage 过程卸载包:

UnloadPackage(PackageModule);

15、保存工程。

下面是OnClick事件处理器的完整清单:

procedure TForm1.Button1Click(Sender: Tobject);
var
PackageModule: HModule;
AClass: TPersistentClass;
begin
PackageModule := LoadPackage(&#39;Package1.bpl&#39;);
if PackageModule <> 0 then
begin
AClass := GetClass(&#39;TForm2&#39;);
if AClass <> nil then
with TComponentClass(AClass).Create(Application) as TcustomForm do
begin
ShowModal;
Free;
end;
UnloadPackage(PackageModule);
end;
end;
登入後複製

不幸的是,并不是这样就万事大吉了。

问题在于,GetClass函数只能搜索到已经注册的类型。 通常在窗体中引用的窗体类和组件类会在窗体装载时自动注册。但是在我们的例子中,窗体无法提前装载。那么我们在哪里注册类型呢?答案是,在包中。包中的每 个单元都会在包装载的时候初始化,并在包卸载时清理。

现在回到我们的例子中:

1、 在工程管理器双击“Package1.bpl”;

2、 点击“Contains”部分“Unit2”旁的+号;

3、 双击“Unit2.pas”激活单元源代码编辑器;

4、 在文件的最后加入initialization部分;

5、 使用RegisterClass过程注册窗体的类型:

RegisterClass(TForm2);

6、 添加一个finalization部分;

7、 使用UnRegisterClass过程反注册窗体的类 型:

UnRegisterClass(TForm2);

8、 最后,保存并编译包。

现在我们可以安全的运行“Project1”,它还会像从前 一样工作,但是现在你可以随心所欲的装载包了。

尾声

记住,无论你是静态还是动态的使用包,都要打开Project | Options | Packages | Build with runtime packages 选项。

在你卸载一个包之前,记得销毁所有该包中的类对象,并反注册所有已注册的类。下面的过程可能会对你有所帮助:

procedure DoUnloadPackage(Module: HModule);
var
i: Integer;
M: TMemoryBasicInformation;
begin
for i := Application.ComponentCount - 1 downto 0 do
begin
VirtualQuery(GetClass(Application.Components[i].ClassName), M, Sizeof(M));
if (Module = 0) or (HMODULE(M.AllocationBase) = Module) then
Application.Components[i].Free;
end;
UnregisterModuleClasses(Module);
UnloadPackage(Module);
end;
登入後複製

在装载包之前,应用程序需要知道所有已注册类的名字。改善这一情况的方法是建立一个注册机制,以便告诉应用程序所有 由包注册的类的名字。

实例

多重包:包不支持循环引用。也就是说,一个单元不能引用一个已经引用了该单元的单元(嘿嘿)。这使得调用窗体中的某 些值难以由被调用的方法设置。

解决这个问题的方法是,创建一些额外的包,这些包同时由调用对象和包中的对象引用。设想一下我们如何使Application成为所有窗体的拥有者?变量Application创 建于Forms.pas 中,并包含在VCL50.bpl包 中。你大概注意到了你的应用程序既要将VCL50.pas编译进来,也同时你的包也需要(require) VCL50。

在我們第三個例子中,我們設計一個應用程式來顯示客戶訊息,並且可根據需要(動態)顯示客戶訂單。

那我們可以從哪裡開始呢?像所有的資料庫應用程式

程式一樣,我們需要連接。我們建立一個主資料模組,包含一個TDataBase連 接。然後我們將這個資料模組封裝在一個套件中(cst_main)。

現在在應用程式中,我們建立一個客戶窗體,並引用DataModuleMain(我 們靜態的連結VCL50 和cst_main)。

然後我們建立一個新的套件(cst_ordr),包中包含客戶 訂單窗體,並require cst_main。現在我們可以在應用程式中動態的裝載cst_ordr了。既然在動態套件裝載以前主資料模組已經存在,cst_ordr就 可以直接使用應用程式的主資料模組實例了。

上圖是此應用程式的功能示意圖:

可換套件:套件的另一個應用實例是建立可更換套件。實現這個功能並不需要套件的動態裝載能力。假設我們要發布一個有時間限制 的試用版的程序,如何實現這一點?

首先我們創建一個「Splash」窗體,通常情況下是一幅帶有「試用」字樣的圖片,並在應用程式啟動的過程中顯示它。然後我們建立一個「About」窗體,提 供一些關於應用程式的資訊。最後,我們建立一個用於測試軟體是否過期的函數。我們把這兩個窗體和這個函數封裝到一個包中,並將它隨試用版軟體發布。

對於付費版軟體,我們也創建一個「Splash」窗體和一個「About」窗體——要和前面的兩個窗體類名相同——以及一個測試函數(什麼也不做),並將它們封裝到同名的包中。

什麼什麼?你問這有用麼?好吧,我們可以公開的發布一個試用版軟體。如果某個客戶購買了該應用程序,我們只需要發送 非試用版的包。這就大大簡化了軟體的發布過程,因為只需要一次安裝和一次註冊包升級。

套件為Delphi和C++ Builder開發社群打開了另一扇通往模組化設計的大門。透過包你不再需要到處傳遞窗體句柄,不再需要回呼函數,不再需要其它DLL技術。由此也縮短了模組化程式設計的開發週期。我們所要做的僅僅是讓Delphi的套件為我們工作。


相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門推薦
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板