摘 要 使用动态连接库(DLL)可以将资源、函数、过程等封装,同样也可以将系统需要调用的子窗体封装起来,在需要时调用。不仅能够节省系统资源,同时也有利于整个软件的模块化处理。Delphi是Windows操作系统中流行的快速开发工具,可以方便的创建和调用动态连接库。本文中,介绍了使用Delphi来完成在动态连接库中封装及调用子窗体的完整过程。
关键词 封装 动态连接库 子窗体 Delphi
在Windows的应用程序中,MDI类型的应用程序非常普遍,对于子窗体的调用,则是各有其法。在Windows操作系统中,大量使用到了DLL(动态连接库),通过这种方式可以将程序设计模块化,在系统更新时较为方便。
1 Delphi中对于DLL的支持
Delphi是Borland公司推出的基于Windows操作系统的快速开发工具,它能够很轻松的完成Windows各种应用的开发,同时,也具有编写、调用DLL的能力。
在Delphi中调用DLL哪谌萦辛街址绞剑 蔡 饔糜攵 饔谩?lt;/DIV>
静态调用是指在Delphi中事先声明该函数或者过程的定义,然后既可以按照声明的名称来调用该函数。例如,假如我们要调用Windows的kernel32.dll中的Sleep和SleepEx这样的两个方法,那么就可以事先声明如下:
procedure Sleep; external kernel32 name 'Sleep';
function SleepEx; external kernel32 name 'SleepEx';
这样我们就可以直接使用Sleep和SleepEx来直接调用kernel.dll中的“Sleep”和“SleepEx”方法了。
静态调用使用比较简单,在对于Windows系统自身的DLL中的函数或者过程的调用时,使用较为普遍。上诉所举的两个方法,实际上就是在Delphi的Windows.pas单元中的静态调用声明。
实际上,Windows还提供了另外一种较为复杂的调用方式,即动态调用。
使用动态调用有两个步骤:
⑴ 声明将要调用的DLL内部方法类型为一个对象,例如:
TDllBegin = function(Info : TDllParam; var FormInfo : TFormInfo) : Integer ; StdCall;
⑵ 使用Windows API来动态调用DLL中的方法。
需要使用到三个Windows API函数,分别是:
加载库文件:LoadLibrary;
寻找指定方法的地址:GetProcAddress;
释放库文件资源:FreeLibrary。
下面是一个简单的例子:
2
3 H : THandle;
4
5 Proc : TDllBegin;
6
7 pProc : TFarProc;
8
9 ADllFormInfo : TFormInfo;
10
11 begin
12
13 H : = SafeLoadLibrary( ' F001.dll ' ); // 假设F001.DLL为目标DLL文件
14
15 if H <> 0 then
16
17 pProc : = GetProcAddress(H, pAnsiChar( ' DllBeginFunction ' ));
18
19 if pProc <> nil then
20
21 begin
22
23 Proc : = TDllBegin (pProc);
24
25 if Proc(GetDllParam, ADllFormInfo) = 1 then
26
27 FrmList.Add(ADllFormInfo); // 将返回的DLL子窗体信息保存到链表中
28
29 end ;
30
31 end ;
在Delphi中提供了一个封装的加载DLL的函数——SafeLoadLibrary,其声明如下:
function SafeLoadLibrary(const Filename: string; ErrorMode: UINT = SEM_NOOPENFILEERRORBOX): HMODULE;
可以使用该函数来替代Windows API LoadLibrary,按照Borland公司的说法,它可以“安全而简单的加载库文件”,其返回值与LoadLibrary一致。
相对于静态调用,动态调用较为复杂,而且还要涉及到过程指针等概念,使用者需要有一定的经验方能使用自如;但是相对于静态调用,动态调用却也有较为灵活的优点,应根据实际情况灵活选用。
在本文中,则主要介绍使用动态调用的方法来调用DLL中的子窗体。
2 Delphi中对于窗体(Form)的显示方法
一般而言,在Delphi中调用窗体有两种方式:模态调用与非模态调用。对应的方法是ShowModal与Show。
在MDI应用程序中,一般对于子窗体均使用Show的方式来显示,即非模态调用。这样做的好处是,即使同时打开了多个子窗体,在窗体之间的切换也非常容易,不会像模态调用那样,出现对一个窗体的操作不结束就无法继续的现象。
因此,在本文中,主要介绍使用非模态方式调用DLL中的子窗体。
3 编写包含子窗体的DLL文件
在Delphi中编写DLL是比较简单的,新建一个DLL项目之后,可以看到在该项目文件(*.DPR)中包含了如下的内容:
2
3
4 uses
5
6 SysUtils,
7
8 Classes;
9
10
11 { $R *.res }
12
13
14 begin
15
16 end .
需要注意的是,如果在传递给DLL中方法的参数中包含有String类型的变量(包括结构体中的String变量,以及单独作为参数的String变量),需要在Uses子句的第一位添加上ShareMem单元文件,同时在调用该DLL的主程序项目文件的Uses子句第一位添加上该单元文件。在发布该程序的时候,需要将BorlandMM.DLL文件同时发布。
当然,也可以通过将String类型替换为PChar类型来避免这个问题,这样还可以保证其他的语言调用该DLL内部的方法。其实就是做到与C/C++相兼容。
在建立完一个DLL的项目文件之后,向该项目添加窗体,要将该项目的主窗体的FormStyle社定为fsMDIForm,这样才能保证在主程序中调用该子窗体。
接下来的工作则和普通的Delphi程序设计完全一样,诸如添加控件、编写事件等等。
4 调用DLL中的子窗体
前面的所有工作完成以后,就可以开始从主程序调用封装在DLL中的子窗体。
在调用之前,首先需要了解一个事实:主程序的Application与DLL的Application实际上是两个不同的东西。这就是说,直接使用DLL自身的Application变量肯定得不到预期的结果。
解决的方法有两个:
⑴ 主程序的Application句柄传递给DLL,并覆盖之。
⑵ 主程序的Application直接传递给DLL,并覆盖之。
同理,也可以把主程序的Screen等全局变量传递给DLL,这样DLL实际使用的就是主程序的全局变量,也只有这样才能顺利的调用封装在DLL中的子窗体。
除此之外,前面介绍的在动态调用DLL内方法的时候,指出了在调用完毕要使用FreeLibirary来释放资源,但是在调用子窗体的时候,却不能立刻就对获得的资源进行释放,否则就会出现错误。
因此,对于这种调用,要使用另外一种方法,即将使用LoadLibirary返回的句柄保存起来,在需要释放的时候(例如,制定关闭某个打开的子窗体、关闭所有的子窗体等等),才是用FreeLibirary来释放打开的DLL资源。
为了完成上诉的任务,需要声明两个结构体,分别保存传递给DLL的参数,以及保存调用的DLL的信息,如下:
TDllParam = packed record
…
Application : TApplication;//此处直接将Application变量传递给DLL
Screen : TScreen;
…
end;
TFormInfo = packed record
…
DllForm : THandle;//保存使用LoadLibrary返回的值
end;
在完成结构体定义之后,最后再来看看DLL项目文件中对外导出的方法定义。因为在调用子窗体的时候,实际上就是调用该方法。
function DllBegin (SysInfo: TDllParam; var FormInfo: TFormInfo): Integer; stdcall;
begin
Application := SysInfo.Application;//将DLL的Application对象强行设为主程序的Application变量
…
try
Result := 0;
if Application.FindComponent('TInfoDllMainForm') = nil then
begin
ADllForm := TTInfoDllMainForm.Create(Application, SysInfo);
ADllForm.Show;
…
Result := 1;
end;
ADllForm.BringToFront;
except
Result := -1;
end;
end;
…
exports
DllBegin,
…
对于MDI应用的子窗体,如果在其主窗体的FormClose事件中声明代码:
Action := caFree;
那么在关闭此子窗体的时候,就会自动的销毁该窗体,否则,关闭该子窗体只能让该窗体最小化。
假如在FormClose事件中加入了该行代码,则出现了一个问题,用户关闭了某个子窗体,但是在主程序中仍然保存了调用该子窗体的DLL句柄等信息,那么就非常容易产生异常,解决的方法同样有两个:
⑴ 在FormClose事件中不使用Action := caFree;的代码,所有子窗体的关闭操作全部由主程序来完成。显然,这是一个非常简单的处理方法,用户也无法真正的关闭子窗体,只能使其最小化。
⑵ 使用消息。自己定义一个消息,在子窗体关闭的时候触发这个消息,在主程序定义该消息的处理事件,主要是使用FreeLibirary来释放相对应的资源等操作,这样就可以解决前面提及的问题。
至此,调用封装在DLL内的子窗体整个过程结束。由于所有打开的子窗体信息均保存在链表中,可以对该链表进行各种操作,包括关闭指定的子窗体(同时释放相关的DLL资源等等)。
5 结束语
使用DLL封装窗体,不仅仅对于软件的模块化有益处,而且还对于软件的编写、调试、维护以及升级均有裨益。当然,相对于将主程序、子窗体编写在一起的方式,这种方式显得较为复杂,而且编写并不是那么直观。但是,对于大型程序的编写,多人并行开发,这种方式则又有其独到的优势所在:只要编写好调用的主程序,其下的各个子模块均封装在各自的DLL中,则可以多人同时开始并行开发,独立完成单元测试,这将大大加快整个软件的开发过程。