关于包的问题,如果你能给我答案,你就是我爷爷了!!!(200分)

  • 主题发起人 主题发起人 longwei
  • 开始时间 开始时间
L

longwei

Unregistered / Unconfirmed
GUEST, unregistred user!
在主程序PMain.exe中动态调用包含窗体FBase2的Basepkg2.bpl,而TBase2继承于TBase1=TForm,TBase1被包在
Basepkg.bpl这个运行期包中,这一切都正常,问题在于当我修改TBase1,具体是增加一些全程变量,如private,
public或protected变量后并重编译Basepkg.bpl,然后主程序PMain.exe再动态调用包含窗体Base2的Basepkg2.bpl,
(Basepkg2.bpl没有重新编译),并使用新增的变量,会出现如下错误:Project PMain.exe raised exception
class EAccessViolation with message'Access violation Project PMain.exe raised exception class
EAccessViolation with message'Access violation at address ???????? in module 'rtl60.bpl, ...
或其他内存访问错误,重新编译Basepkg2.bpl就没问题。

编译PMain.exe需要Build with runtime packages (Basepkg).

我的问题是在我的一个大系统中,所有的子模块所继承的基类都包含于一个运行期包中,基类经常需要修改或升级,
但我不能因此而去重新编译众多的子模块包。子模块包动态调用。


原代码如下:

主程序:
unit CMain;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,db, CBase1;

type
TFbasemodClass = class of TBase1;
TMain = class(TForm)
Button4: TButton;
procedure Button4Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Main: TMain;

implementation

{$R *.dfm}

procedure TMain.Button4Click(Sender: TObject);
var
hModule: THandle;
vTBase2: TBase1;
FBasemodClass :TFbasemodClass;
begin
hModule := LoadPackage('C:/Test/Basepkg2.bpl');
try
vTBase2 := TFbasemodClass(FindClass('TBase2')).Create(self);
vTBase2.ShowModal;
finally
vTBase2.Free;
UnloadPackage(hModule);
end;
end;
end.

运行期包Basepkg.bpl
package Basepkg;

{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}
{$DEBUGINFO ON}
{$EXTENDEDSYNTAX ON}
{$IMPORTEDDATA ON}
{$IOCHECKS ON}
{$LOCALSYMBOLS ON}
{$LONGSTRINGS ON}
{$OPENSTRINGS ON}
{$OPTIMIZATION ON}
{$OVERFLOWCHECKS OFF}
{$RANGECHECKS OFF}
{$REFERENCEINFO ON}
{$SAFEDIVIDE OFF}
{$STACKFRAMES OFF}
{$TYPEDADDRESS OFF}
{$VARSTRINGCHECKS ON}
{$WRITEABLECONST OFF}
{$MINENUMSIZE 1}
{$IMAGEBASE $400000}
{$IMPLICITBUILD OFF}

requires
rtl,
vcl,
dbrtl;

contains
CBase1 in 'CBase1.pas' {Base1};

end.

基类:TBase1
unit CBase1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,db;

type
TBase1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
protected
Property1:String;
Property2:integer; //如果新增全程变量,重编译包Basepkg.bpl,而不
//编译Basepkg2.bpl,会引起以上所描述的问题。
//而修改或新增基类方法或过程不会有问题
end;

var
Base1: TBase1;

implementation

{$R *.dfm}

procedure TBase1.Button1Click(Sender: TObject);
begin
Property1:='Property1';
Property2:=10;
ShowMessage(Property1+' '+IntToStr(Property2));
end;

end.

动态包:Basepkg2.bpl
package Basepkg2;

{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}
{$DEBUGINFO ON}
{$EXTENDEDSYNTAX ON}
{$IMPORTEDDATA ON}
{$IOCHECKS ON}
{$LOCALSYMBOLS ON}
{$LONGSTRINGS ON}
{$OPENSTRINGS ON}
{$OPTIMIZATION ON}
{$OVERFLOWCHECKS OFF}
{$RANGECHECKS OFF}
{$REFERENCEINFO ON}
{$SAFEDIVIDE OFF}
{$STACKFRAMES OFF}
{$TYPEDADDRESS OFF}
{$VARSTRINGCHECKS ON}
{$WRITEABLECONST OFF}
{$MINENUMSIZE 1}
{$IMAGEBASE $400000}
{$IMPLICITBUILD OFF}

requires
rtl,
Basepkg; //包含基类包

contains
CBase2 in 'CBase2.pas' {Base2};

end.

继承类:TBase2
unit CBase2;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, CBase1, StdCtrls;

type
TBase2 = class(TBase1)
Memo1: TMemo;
private
{ Private declarations }
public
{ Public declarations }
end;

var
Base2: TBase2;

implementation

{$R *.dfm}
initialization

RegisterClass(TBase2);

finalization

UnRegisterClass(TBase2);

end.

 
呵呵,这个是borland设计的。
你不重新编译,他根据旧的相对位置找不到相应的声明于地址,
当然出错了!

 
呵呵,你找到爷爷了
 
父类型定义变了,如果没有改变声明形式(包括增加和减少属性),是可以不用重新编译子类的,
可是父类的声明形式变了(不管你是增加还是减少属性),就不是一个东西了,继承子类当然也要重新编译。。。
(这是对象概念的解释,反映在实际上就是类属性相对地址、方法入口地址都变化了,子类就要调用会搞错内存地址了)
看过《回到未来》吗,就好象时光倒流,如果当时主人公的父母没有相爱,那么主人公就从照片上消失了,
于是他想办法促成了他父母的相爱,虽然被他刚回到过去时给破坏了,后来终于促成了这个结果(相当于声明形式没变)
虽然相爱的过程换了一种方式。。。但不影响以后的发展也没关西。。。否则就要时空错乱了哦
所以你不要轻易产生改变父类的念头哦~~:)
 
是啊,好像没有听说过解决办法。
 
标准的OO是定义好基类,然后在上面不断修改扩充功能。你怎么倒着用了,这不是等着
出问题吗?
你可以考虑使用COM/COM+。实现IDispatch接口,然后使用late binding。
 
hModule := LoadPackage('C:/Test/Basepkg2.bpl');
try
vTBase2 := TFbasemodClass(FindClass('TBase2')).Create(self);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~[?]
vTBase2.ShowModal;
finally
vTBase2.Free;
UnloadPackage(hModule);
end;
由于对TBase2作了强制类型转换,而在未重新编译的包中其类定义中缺少新的增加的
部分,因此会出错。
建议:
主模块/基础模块设计好,负责完成交互功能,不再修改
各功能包可使用基础包,使用其中对象(尽量少直接继承)来完成与主模块的
交互,创建新类完成各自的业务功能,互相之间独立开发和升级。
参考一下插件/设计模式等知识。
 
谢谢各位热心朋友解答问题thtfdyh,轻松虎,yidaoh,都说得有道理。送分了
 
何使用Delphi開發大型主從架構系統-Package的秘密和威力
  相信許多人和我一樣,在使用Delphi開發應用系統的時候,一定會想到如何的切割整個應用系統。是把所有的子系統撰寫成一個很大(可能會有數M.Bytes的大小)的EXE檔呢?還是應該把每一個模組撰寫在不同的EXE檔案之中的好。
  事實上這兩種方法都有它們自己的問題。如果是把所有的模組撰寫在一個EXE檔案之中的話,那麼不但執行檔太大,不易更新和維護。在開發時也不甚方便,因為要讓數個人撰寫同一支應用程式的確比較麻煩。那麼如果我們把每一個模組讓不同的程式師撰寫成獨立的EXE檔案,再由一個主程式分別啟動不同的EXE檔不就好了嗎?沒錯這是許多人使用的方法(包括我在內),但是這樣切割應用系統有一個問題,那就是如果每一個獨立的EXE模組,都需要使用資料庫的話,那麼當主程式啟動個別的EXE檔案時,每一個EXE都必須重新再連結到資料庫,開啟資料庫表格,再取得它需要的資料。這個過程通常都需要不少的時間。例如連結到Oracle並且開啟一個資料庫表格的話,通常需要五到十秒。如果EXE開啟的資料庫表格或是查詢比較多的話,那麼主程式在啟動獨立的EXE檔案時,通常需要30幾秒到一分鐘不等。這對於許多的使用者而言是非常不方便的。這樣的狀狀甚至會造成你的專案無法交貨(例如使用者要求在五秒之內EXE程式的畫面必須出現)。除此之外,每一個獨立的EXE又使用了額外的連結以便存取資料庫,造成了資源的浪費。面對這種二難的局面,你現在的選擇是什麼呢?
  事實上這個問題在我的心中也盤旋了許久。因為這一是我想要解決的問題,只是由於工作的繁忙讓我一直無法花時間解決它。最近在手上的事情告一段落之後,又接到許多朋友的詢問,所以決定花一些時間試著解決這個重要的問題。
增加應用程式載入的效率
  如果我們仔細的思考這個問題的話,就可以發現問題出在每一支獨立的EXE都需要重複的連結資料庫所至。所以如果我們可以讓連結到資料庫的次數減少的話,不就可以加快應用程式載入的效率了嗎?
  這個想法當然很簡單,但是問題是要如何的減少應用程式連結資料庫的次數呢?事實上這個想法也很簡單,最好是讓應用系統連結資料庫的次數變成一次,如此一來除了主程式需要連結資料庫之外,其他的應用模組都能夠公同使用由主程式載入的資料模組的話,那麼一切問題不都解決了嗎?請注意,在這裡我所說的其他模組代表獨立的EXE或是其他形式的應用程式,而不是指在單一一個EXE之中不同的表格或是子系統。
  我們可以使用圖一來表示這個想法。在這個構想中,我希望由應用主程式先負責載入公用的資料模組。在這個資料模組之中有其他子系統需要使用各個資料庫表格,如此一來當主程式啟動其他的子系統時,就不需要再讓每一個子系統再連結,開啟資料庫表格了。

圖一 公用資料模組示意圖
  當這樣還有一些設計上的問題,我們稍後再回來討論這個問題,現在先我們看看如何的把這個構想實做出來,並且測試一下實際的結果是不是真的比較有效率。
  要實做這個構想,我們必須想辦法讓公用的資料模組能夠讓每一個子系統存取到,並且不再需要每一個子系統都分別的和資料庫建立一個連結的Session。我的第一個想法是使用DLL來解決這個問題。但是事實上使用DLL無法解決這個問題。在我花費了許多的時間之後,使用DLL仍然有會有AccessViolation的錯誤。我也曾在網路上搜尋相關的問題或是資料,我在寶蘭的DiscussForum中也看到有人提出類似的問題,但是似乎都沒有人確實的回答這個問題。我也想過乾脆拿出我的Soft-Ice和Bounds-Checker看看為什麼使用DLLAssembly打交道,這實在不是件好玩的事情。正打算放棄之時,突然想到Delphi3.0之中的Package不正是解決這個問題的好方法嗎?於是我就決定試試看,果然一擊中地,順利的解決了這個問題。當然,要知道為什麼使用Package可以解決這個問題,你需要知道Package和DLL的異同。
DLL和Package
  為什麼在我一開始使用DLL時無法解決多個模組共用一個資料模組DLL的問題呢?這主要是因為在Win95/NT中當每一個模組載入DLL時,對於每一個DLL之中的全域變數而言,每一個模組都會有一份獨立的變數。這是什麼意思呢?我們可以使用圖二來說明。

圖二Win95/NT中全域變數和模組的關係
  當圖二中的模組一和模組二分別的載入它們共用的DLL時,雖然在這個共用的DLL中有一個全域變數gAccount。但是模組一和模型二會分別的擁有一個gAccount變數。這也就是說模組一對於gAccount變數數值所做的修改並不會影響模組二中的gAccount變數數值。在Wn95/NT中的DLL行為是和Win3.x不同的,因為在Win3.x中所有的模組都使是共用一份DLL中的全域變數。
  由於Win95/NT中DLL全域變數的這種特性,所以當你想把資料模組撰寫在DLL之中讓多個模組共同使用時,問題就來了。因為在這種情形下,每一個模組都會有一份它自己的資料模組。所以當每一個應用程式模組載入資料模組的DLL時,它仍然會連結資料庫一次,所以你並無法減少連結資料庫的次數。
  那麼使用Package有什麼不同嗎?在回答這個問題之前,請你先回想一下。在Delphi3.x中它允許你使用Package的功能來編譯你的應用程式,如圖三所示。

圖三Delphi3.x允許你使用Package的功能編譯應用程式
  使用Package編譯應用程式的好處除了可以減少應用程式的大小之外,事實上Package還有一個很重要的特性,那就是Package允許多個模組共用所有的全域變數。
  我們可以使用圖四來說明Package的特性。請你想一想,當你的應用程式使用Package的功能時,事實上它必須載入許多不同的Packages。不管你的應用程式是否使用了全域變數Application,許多的Packages都會使用Application這個全域變數。由於全域變數Application是存在於VCL.DPL之中,所以如果Application會對於每一個載入它的模組都產生一份獨立的全域變數的話,那麼整個應用程式便會產生不正確的結果。所以由這個說明我們可以知道,在圖四中的Application和Screen等全域變數對於所有使用它的模組而言一定是只有一份全域變數。

圖四Package中全域變數的特性
  事實上在Forms.PAS之中的程式碼也透露著這些蛛絲馬跡。例如下面便是Forms.PAS宣告Application和Screen這二個變數的程式碼。從它們的注釋中我們可以清楚的看到,它們是全域物件,即使是編譯成Package時也是一樣。
{Globalobjects}
var
Application:TApplication
Screen:TScreen
Ctl3DBtnWndProc:Pointer=nil;

  由於Package能夠自動的將其中的全域變數編譯成所有使用它的模組都共用一份的特性。所以我們就可以使用這個特性重新的建構圖一的架構成為圖五的形式。

圖五改良過的公用資料模組示意圖
  在圖五中我們可以把所有模組需要共同使用的資料模組撰寫在一個Package之中,然後再把每一個子系統撰寫成獨立的Package。只有主程式是EXE,它負責載入共用的資料模組,以及在使用者啟動每一個子系統時再載入相對應的子系統Package。
  使用這種架構有許多的好處。第一個便是共用的資料模組只需要載入一次即可。而這個好處便是我們前面討論需要解決的問題。如此一來在每一個子系統載入時便可以加快其執行的速度。第二個好處是在開發這整個系統時,我們仍然可以讓不同的程式師負責發展不同的子系統,這樣可以就可以解決前面討論的分工的問題。此外如果一個子系統很龐大的話,你也可以再次的切割這個子系統成為更多的小系統。例如你可以再把圖五中的會計子系統Package再分為一般會計,成本會計,和管理會計等不同的Package。第三個好處是使用Package就像是使用DLL一樣,主程式可以在需要的時候才載入一個Package,在使用完畢之後可以立刻的釋放這個Package,這樣可以讓你對於系統資源有更好的控制能力。最後一個好處是使用Package可以讓你發展出Plug-and-Play的模組,當然這需要藉由結合虛擬介面或是虛擬類別的功能。藉由使用Package和虛擬介面的能力,你可以任意的替換應用系統之中的模組而不會影響系統的執行。這個功能對於許多使用Delphi開發套裝軟體的程式師來說是一個非常重要的功能。
  現在你應該對於DLL和Package的差異有了基本的瞭解,現在是讓我們看看如何使用Package的特性解決我們面對的問題的時候了。下一小節就讓我們實際的撰寫一個範例來證明Package對於全域變數的處理方式以及使用Package的確能夠加快應用程式的載入速度。
實際的範例
  由於平日我大部份的時間都是使用Oracle,MSSQLServer和InterBase(許多的讀者都詢問我為什麼不使用Sybase或是Informix做為範例說明,這實在是因為我比較少使用它們,並沒有其他的意思,所以請使用Sybase,Informix和DB2的讀者見諒。不過我相信我們討論的東西都可以使用在這些資料庫之上)。在這三個資料庫中,Oracle的連結速度一直都是令我非常頭大的,因為在這三者之中,Orcale連結資料庫和開啟資料庫表格的時間最久。所以本節的範例就以Oracle資料庫為範例,看看使用了Package之後會不會有任何明顯的改善。
  首先請你先建立一個Package,並且在這個Package之中產生一個資料模組,並且使用Database和Query元件連結到Oracle的資料庫如圖六所示。

圖六存在於Package之中的資料
模組使用Database連結到Oracle
  在成功的編譯了這個Package之後,再讓我們設計範例程式的主表格如圖七一樣。

圖七使用公用資料模組的主表格
  在主表格中有一個DataSource元件。這個DataSource元件會在資料模組的Package載入之後,連結到資料模組之中的Query元件以便顯示Oracle資料庫之中的資料。現在剩下的工作便是撰寫程式碼載入資料模組Package和顯示資料庫的資料。
  首先在主表格啟動時,它必須在FormActivate事件處理函數中載入資料模組Package。它呼叫LoaddbPackage這個程序。
procedureTMainForm.FormActivate(Sender:TObject);
begin
LoaddbPackage;
end;

  LoaddbPackage是真正負責載入Package的程序。它在一個tryexcept程式區塊中呼叫Delphi的LoadPackge函數載入指定名稱的Package。這個函數在成功執行後會回傳一個Package的handle值。我們的程式必須儲存這個handle值以便稍後使用它,並且在最後藉由這個handle值釋放載入的Package。如果呼叫LoadPackage成功的話,程式就呼叫LoadDataModule從Package中取得前面介紹的資料模組,否則在except程式區塊中ShowMessage會顯示發生錯誤的原因。
procedureTMainForm.LoaddbPackage;
begin
//我們必須載入資料庫Package以便連結到資料庫
try
aDBConnect:=LoadPackage(DBPackages);
LoadDataModule;
except
onE:Exceptiondo
begin
MessageBeep(Word(-1));
ShowMessage(E.Message);
Application.Terminate;
end;
end;
end;

  在LoadDataModule中我們必須先從資料模組Package之中取得資料模組的真正Meta-Class名稱,然後使用這個Meta-Class建立真正的資料模組物件。所以LoadDataModule一開始會呼叫GetClass向Windows取得特定類別名稱的Meta-Class。而GetClass傳入的參數『TConcreteDataModule』,便是前面我們建立的資料模組的真正的類別名稱。
  由於一個獨立的EXE要能夠取得Package之中的Meta-Class必須使用指定的類別名稱。所以當你在Package中撰寫任何的類別時,你必須確定這個類別名稱在所有Delphi或是應用程式載入的Package中都是唯一的。否則如果有在所有載入的Pakcage中有相同的類別名稱時,Delphi或是應用程式會產生一個例外。
  在成功的從Package取得了Meta-Class之後,你就必須使用這個Meta-Class產生真正的資料模組物件。請注意在下面的程式碼中,我們使用了強制型態轉換把TComponentClass的物件轉換為TDataModule的物件。
  在建立了TDataModule物件之後,我們就可以一一的搜尋資料模組之中的元件並且找到我們需要的Query元件,並且把主表格中DataSource的DataSet特性值設定為找到的Query元件。
procedureTMainForm.LoadDataModule;
var
iCounter:Integer;
begin
{NotethatTApplication"owns"thisformandthusitmustbefreedprior
tounloadingthepackage}
dataModuleClass:=GetClass('TConcreteDataModule');
ifdataModuleClass<>nilthen
begin
admGlobal:=
TDataModule(TComponentClass(dataModuleClass).Create(Application));

foriCounter:=0toadmGlobal.ComponentCount-1do
begin
ifUpperCase(admGlobal.Components[iCounter].ClassName)='TQUERY'then
begin
aQuery:=TQuery(admGlobal.Components[iCounter]);
DataSource1.DataSet:=aQuery;
break;
end;
end;
end;
end;

  由於在上面的程式碼中我們使用了GetClass以取得特定名稱的Meta-Class,所以你的資料模組Package必須在它的initialization程式區塊中先註冊它自己。下面的程式碼便是資料模組Package註冊的程式碼。
initialization
RegisterClass(TConcreteDataModule)
  現在就可以執行範例程式了,在主程式執行之後,你可以看到類似圖八的畫面。從主表格中我們可以證明的確可以藉由資料模組Package存取到Oracle的資料。

圖八主表格執行的畫面,它果然可以藉由資料模組Package存取資料
  到這裡為止我們只是證明了使用資料模組Package可以讓應用程式正確的執行,但是主程式啟動的時間和一般獨立的EXE沒有什麼不同。但是接下來的情形就比較有趣了。因為我們要藉由接下來的程式碼來證明使用Package可以大幅加快子系統的載入速度。
  在主表格中有一個按鈕『載入第二個UIPackage』。這個按鈕的功能就是模擬載入子系統的功能。當使用者點選這個按鈕之後,範例程式會載入另外一個只有使用者介面的Package,而且這個Package必須再使用剛才載入的資料模組Package來顯示資料。藉由這個模擬的子系統功能,我們要證明資料模組Package可以在不同的模組中共用而且可以加快模組啟動的時間。
  這個按鈕的OnClick事件處理函數和主表格的FormActvate事件處理函數非常的類似,它也是呼叫LoadUIPackage程序以便載入一個使用者介面Package。
procedureTMainForm.Button1Click(Sender:TObject);
begin
LoadUIPackage;
end;

  LoadUIPackage和LoaddbPackage幾乎一模一樣,它也是呼叫Delphi的LoadPackage和GetClass載入Package,取得使用者介面表格的Meta-Class,建立使用者介面表格物件,搜尋資料模組中的Query元件,然後顯示資料庫的資料。
procedureTMainForm.LoadUIPackage;
begin
//我們必須載入使用者介面Package以便連結到資料庫
try
UIConnect:=LoadPackage(UIPackages);
LoadUIModule;
except
onE:Exceptiondo
begin
MessageBeep(Word(-1));
ShowMessage(E.Message);
Application.Terminate;
end;
end;
end;
procedureTMainForm.LoadUIModule;
var
iCounter:Integer;
aDS:TDataSource;
begin
{NotethatTApplication"owns"thisformandthusitmustbefreedprior
tounloadingthepackage}
pkgModuleClass:=GetClass('TUIPackageForm');
ifpkgModuleClass<>nilthen
begin
aPkgForm:=TCustomForm(TComponentClass(pkgModuleClass).Create(Application));
foriCounter:=0toaPkgForm.ComponentCount-1do
begin
ifUpperCase(aPkgForm.Components[iCounter].ClassName)='TDATASOURCE'then
begin
aDS:=TDataSource(aPkgForm.Components[iCounter]);
aDS.DataSet:=aQuery;
break;
end;
end;
aPkgForm.Visible:=True;
end;
end;

  當我們完成載入使用者介面Package的功能,再執行範例程式並且按下『載入第二個UIPackage』按鈕之後,可以看到類似圖九的畫面。

圖九第二個使用者介面Package啟動的畫面
  令人驚訝的是,使用者介面Package之中的表格會立刻的顯示出來,幾乎完全不需要等待。表格一是我使用這個範例程式和使用二個獨立的EXE應用程式比較的結果。從表格中你可以看到使用Package載入子系統比使用EXE載入子系統整整快了十倍。
 使用獨立的EXE應用程式使用EXE加Package的功能
主程式啟動時間2020
其餘模組啟動時間202
表格一獨立的EXE和使用Package的EXE執行的效率比較

圖十使用Package的應用程式和一般應用程式執行效率的比較
  從上面的結果我們就可以知道使用Package的好處,它不但可以讓我們共用資源,也能夠改善子系統啟動的時間。
  當然應用程式在使用完畢之後,它必須釋放動態載入的Package以便釋放系統資源。在主表格的FormClose事件處理函數中它呼叫了UnLoadAddInPackage程序並且傳遞Package的handle值做為參數以便釋放Package。
procedureTMainForm.FormClose(Sender:TObject;varAction:TCloseAction);
begin
UnLoadAddInPackage(dbConnect);
UnLoadAddInPackage(UIConnect);
end;

  UnLoadAddInPackage先從全域物件Application中找到載入的資料模組和使用者介面表格,先釋放這些動態載入,建立的物件,切斷它們和Application和關係,再利用Package的handle值解除Package對Windows的註冊,最後再呼叫UnLoadPackage釋放載入到記憶體之中的Package。
procedureUnLoadAddInPackage(Module:THandle);
var
i:Integer;
M:TMemoryBasicInformation;
begin
fori:=Application.ComponentCount-1downto0do
begin
VirtualQuery(GetClass(Application.Components.ClassName),M,SizeOf(M));
if(Module=0)or(HMODULE(M.AllocationBase)=Module)then
begin
ShowMessage(Application.Components.ClassName);
Application.Components.Free;
end;
end;
UnRegisterModuleClasses(Module);
UnLoadPackage(Module);
end;

  當你結束主程式時,你應該會看到類似圖十一和圖十二的畫面。這些畫面證明了當主程式結束時,它能夠正確的釋放所有載入的Package以釋放系統資源。

圖十一範例程式結束時顯示它正確的釋放了所有使用的Package

圖十二範例程式正確的釋放了公用的資料模組Package
  上面的範例程式證明了使用Package的確能夠公用Package以及Package之中的全域資料,此外也能夠幫助子系統加快載入和啟動的時間。但是主程式在啟動時仍然需要載入資料模組Package,連結資料庫。但是對於許多的使用者而言,他們仍然會希望讓主程式也能夠很快的出現。要達成這個目標,你還需要藉助Delphi執行緒的能力。
加入多執行緒載入的能力
  在上一節中你看到了使用Package的確可以大幅加快子系統載入的時間。但是我們在載入主程式時仍然需要20到30秒的時間,因為主程式還是需要載入共用的資料模組Package。但是對於許多案子來說,使用者經常會要求程式必須在10秒或是5秒之內第一個畫面就必須出現。那麼對於這種要求我們可不可以達到呢?
  如果你想到Delphi中的多工執行緒功能Tthread物件的話,那麼答案就出現了。我們只需要在主程式第一個表格出現之時,啟動一個多執行緒物件讓它負責載入公用的資料模組不就可以了嗎?如此一來主表格可以立刻的出現在螢幕之上,而讓執行緒在背景繼續的載入資料模組。
  為了要加入多執行緒的能力,你必須修改上一節的範例。在範例程式的FormActivate事件處理函數我們不再直接呼叫LoaddbPackage,而是建立一個Tthread物件,然後由這個Tthread物件真正的載入資料模組Package。
procedureTMainForm.FormActivate(Sender:TObject);
begin
DBThread:=TOracleThread.Create(True);
aDBThread.OnTerminate:=dbThreadTerminated;
aDBThread.Resume;
end;

  在TOracleThread的Execute虛擬方法中,它呼叫了LoaddbPackage載入資料模組。在這裡有一點非常重要的地方便是當你在執行緒中載入Package時,所有的handle值和物件參考值必須儲存在主執行緒的變數之中。否則在這個執行緒執行完畢之後,這些handle值和物件參考值都會成為無效的數值。所以在下面的程式碼中你可以看到執行緒在呼叫LoadPackage載入Package時會把LoadPackage回傳的數值儲存在主表格的變數之中。
procedureTOracleThread.Execute;
begin
{Placethreadcodehere}
LoaddbPackage;
end;
procedureTOracleThread.LoaddbPackage;
begin
//我們必須載入資料庫Package以便連結到資料庫
try
MainForm.aDBConnect:=LoadPackage(DBPackages);
LoadDataModule;
except
onE:Exceptiondo
begin
MessageBeep(Word(-1));
ShowMessage(E.Message);
Application.Terminate;
end;
end;
end;
procedureTOracleThread.LoadDataModule;
var
iCounter:Integer;
begin
{NotethatTApplication"owns"thisformandthusitmustbefreedprior
tounloadingthepackage}
dataModuleClass:=GetClass('TConcreteDataModule');
ifdataModuleClass<>nilthen
begin
MainForm.admGlobal:=TDataModule(TComponentClass(dataModuleClass).Create(Application));
end;
end;

  當我們修改完範例程式之後,就可以試著再次的執行它,看看主程式的載入時間是不是有任何的改善。下面的表格和圖形顯示出當我們使用了執行緒的功能之後,第一支主程式啟動的時間果然大幅減少為3秒鐘。比起原來的20秒果然改善了許多,真是令人印象深刻。
 使用獨立的EXE應用程式使用EXE加Package的功能
主程式啟動時間203
其餘模組啟動時間202
表格二使用執行緒後的範例程式執行效率

圖十三加入執行緒後應用程式執行的效率
  現在使用了執行緒功能之後,不但每一個子系統啟動的時間加快了許多,主程式更是可以在瞬間出現,這樣的執行速度應該是可以讓大部份的人滿意了吧。
應用程式,企業法則(企業物件)的切割
  從上面的範例中我們可以知道,善用Delphi的Package和執行緒的功能可以讓我們大幅的改善應用程式載入和執行的效率。但是上面的範例只是假設在很簡單的狀態之下。在許多實際的案子中,我們可能無法把所有的資料集元件放在一個單一的資料模組之中。在這種情形下,你可以把所有的資料集元件分別撰寫在不同的資料模組Package之中,並且在每一個子系統需要特定的資料模組Package時再載入它們。
  當我們從這個角度觀察應用系統時,可以發現如何的切割資料集到不同的資料模組中似乎是一件非常重要的事情。因為這不但牽涉到應用系統執行的效率,更和系統資源的善用有很大的關係。事實上當我們開發N-Tier的應用系統時,你也會發現如何切割應用程式和企業物件對整個應用系統的架構有深遠的影響。
  所以應用程式和企業物件的切割似乎在未來新一代應用系統的開發中佔有重要的地位。當然要能夠適當,有效率的切割企業物件需要SA在分析系統時好好的做分析的工作,更需要SD能夠通盤的設計整個應用系統運作的架構。
  如果我們結合Delphi強大的N-Tier,Package以及執行緒功能的話,就可以使用如下的圖形來表示。

圖十四N-Tier,Package和執行緒結合使用的架構
  請注意在圖十四中說明了並不是只有資料模組可以存在於Package之中,我們也可以把應用邏輯或是企業物件封裝在Package之中或是ActiveX之中,於應用程式需要時再載入執行它們。最後由於應用程式伺服器在大多數的情形下是執行在WindowsNTServer之中,所以我們可以更有效率的使用作業系統的執行緒能力來載入應用程式需要的Package。
更具威力的功能
  就像我在前面說明的,我無法在一篇文章中為各位介紹所有有關Package的使用方法和各種功能。使用Package更高階和更具威力的功能應該是和虛擬類別結合一起使用。此外Delphi3也提供了許多的程序和函數能夠讓你檢查每一個Package使用了什麼其他的Package,執行時期函式館和全域函式。
  結合Package和虛擬類別不但能夠讓你發展出Plug-and-Play的模組功能,更能夠讓你完全的控制Package中所有的物件和變數。更能夠讓你發展出一些自動化的工具來幫助你開發你的應用系統。我計劃在以後的文章中再繼續和各位討論這些高等的主題。
結論
  在本篇文章中我們看到了如何的使用Delphi3.0中的Package功能來解決應用載入時連結資料庫效率的問題。許多人對於Package的認識只是它可以減少應用程式的檔案大小,除此之外似乎就沒有什麼其他的瞭解了。但是這篇文章就告訴你如何的發掘Package更有威力的一個應用。使用Packages可以立刻的降低模組啟動需要的時間。
  但是我們在這篇文章中就討論了所有有關Package的功能來嗎?那當然不,還需要許多更有威力的功能我並未討論。例如你如果能夠更進一步的結合Package,參考計數值以及抽象虛擬類別(AbstractVirtualClass)或是抽象介面的話,那麼你就可以完全的控制Package中內含的類別以及表格,而不需要一定是Delphi內定的類別和表格。這樣一來你可以設計一個Plug-and-Play的Package介面,以便讓主程式能夠載入任意的Package,存取它功能。此外你也可以取得Package中許多重要的資訊,例如這個Package輸出了那些的方法,使用了那些其他的Package?Package和initialize以及finalization的關係等,也許有機會再讓我們討論這些更有意思的主題。
  事實上從這篇文章中就可以出Delphi的多麼的好用。如果今天你是使用VisualBasic或是PowerBuilder的話,那麼抱歉,你絕對無法解決這個問題,也許再等到下一個VisualBasic或是PowerBuilder版本的話,就有『可能』解決這個問題吧。但是Delphi就不同了,它除了可以讓你撰寫一般的應用系統,但是在遇到特殊的情形時,Delphi也可以在短暫的時間內超越工具目前功能的限制,而解決這些問題,誰是比較好的主從架構開發工具就不言自明了。

                   稿件来源:http://www2.borland.com.tw/tw/reference9.html

 
多人接受答案了。
 
后退
顶部