Delphi – 在DLL中保存并创建MDI子窗体(翻译)

很多人认为,在Delphi的DLL中加载MDI子窗体时,需要注意保存并恢复Delphi全局变量(Application和Screen)对象。其实,根本没有办法在DLL中加载MDI子窗体。

原文出处:http://delphi.about.com/library/weekly/aa020805a.htm


    在开发复杂的应用系统时,把系统分为各个模块分而制之是一种很平常的策略,这样就可以使得每个模块负责一套业务逻辑。模块可以是一个DLL,在需要的时候,主程序可以调用这个DLL以使用该模块。

    当你开发一个MDI应用程序,并希望把该应用程序分割成各个模块时,自然会有这样的疑问:“如何将MDI子窗体创建在DLL中,以使得在必要的时候能够被主程序调用?”

    如果你已经知道如何在DLL中创建非MDI子窗体的窗体,并且也知道如何从主程序调用这个DLL中的窗体时,你或许会认为问题已经得到了解决,但事实并非如此。

    如何让DLL中的MDI子窗体得知哪个窗体是它的父窗体(以便让其能够以子窗体的形式展现出来)?用于创建该子窗体的Application对象(位于父窗体所在的应用程序中)与你在DLL中获得的Application对象根本就是两回事情,不仅如此,两者的Screen对象也不相同。

 

想要在DLL中保存并创建MDI子窗体?没门!

    就如你所看到的这个标题所说的一样,根本就没有办法在DLL中保存并创建MDI子窗体,然后让MDI应用程序去调用它。或者你会说,你已经在网上找到一些资料,上面显示如何保存并传递Application对象,以使得DLL中的MDI子窗体能够被创建于正确的MDI父窗体中,其实这种做法并不奏效,至少在Delphi 5以上的版本中不会奏效。

 

那么解决方案是什么呢?

    如果你非要在DLL中保存并创建MDI子窗体,那么你需要在主程序(MDI应用程序)和DLL生成的时候,与运行时包(runtime package)一起生成。这样做就确保了主程序和DLL使用了共同的Application和Screen对象,并使用了相同的RTL与VCL实例。为了100%确保“安全”,你应该使用包,而不是DLL。

 

包中的MDI子窗体:唯一正确的解决方案

    Delphi的包是一种特殊的DLL,它仅用于Delphi的应用程序。如果你的应用程序模块是使用包的形式开发而不是DLL,那么所有的模块都会共享同一个内存管理机制,包括了VCL全局对象(例如Application和Screen)以及RTL与VCL在内存中的同一代码拷贝。

 

包中的MDI子窗体:实例

    现在来看一个实例,首先,我们将创建一个MDI应用程序:

1、 创建一个新的MDI应用程序。你可以使用MDI应用程序创建向导(File - New - Other - Projects - MDI Application)

2、 确保主窗体的FormStyle属性已经设置为fsMDIForm

3、 添加一个MainMenu控件,使其只有一个用于从包中加载子窗体的菜单项.

MDI module parent

4、 确保在生成应用程序的时候,是和运行时包一起生成的。在Project – Options菜单中,选择Packages选项卡,然后选中“Build with run-time packages”选项。你至少要选中rtl包和vcl包

Project-Options - built with packages

在真正编码前,首先生成该包,并且向其添加一个MDI子窗体

1、 创建一个新的运行时包

2、 向包添加一个TForm对象,确保该对象的FormStyle属性已经设置为fsMDIChild

3、 添加一个导出过程,用于创建子窗体的实例:

procedure TPackageMDIChildForm.FormClose
(Sender: TObject;
   var Action: TCloseAction);
begin
//since this is an MDI child, make sure
//it gets closed when the user
//clicks the x button.
Action := caFree;
end;

procedure ExecuteChild;
begin
TPackageMDIChildForm.Create(Application);
end;

exports
//NOTE!! The export name
//is CASE SENSITIVE
ExecuteChild;

end.

    重新回到MDI主程序,以下是MDI主窗体的所有代码:

type

//signature of the "ExecuteChild"

//procedure from the Package

TExecuteChild = procedure;

 

TMainForm = class(TForm)

   ...

private

    PackageModule : HModule;

    ExecuteChild : TExecuteChild;

    procedure PackageLoad;

end;

 

var

MainForm: TMainForm;

 

implementation

{$R *.dfm}

 

procedure TMainForm.PackageLoad;

begin

//try loading the package

//(let's presume it's in the same

//folder, where the main app. exe is)

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

 

//if loaded, try locating

//the ExecuteChild procedure

if PackageModule <> 0 then

try

    @ExecuteChild := GetProcAddress(PackageModule,

                                    'ExecuteChild');

except

    //display an error message if we fail

    ShowMessage ('Package not found');

end;

end;

 

//menu click

procedure TMainForm.mnuCallFromDLLClick

(Sender: TObject);

begin

//lazzy load package

if PackageModule = 0 then PackageLoad;

 

//if the ExecuteChild procedure

//was found in the package, call it

if Assigned(ExecuteChild) then ExecuteChild;

end;

 

procedure TMainForm.FormDestroy(Sender: TObject);

begin

//if the package was loaded,

//make sure to free the resources

if PackageModule <> 0 then

    UnloadPackage(PackageModule);

end;

 

    上面的代码中,选中菜单项时,主程序动态地加载了所需的包(使用PackageLoad过程),并且在应用程序终止的时候卸载了已经加载的包。最后,在运行时,我们得到了一个能够正确加载并运行包中MDI子窗体的应用程序。

Packaged MDI Child form inside an MDI parent

    最后需要说明的是,在使用runtime package模块化应用程序时,你必须将所需的包与应用程序的exe文件一起发布。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值