本章要点:
DLL的基本概念
DLL的创建
DLL的静态调用方式
DLL的动态调用方式
利用 DLL实现窗体重用第 10章 DLL的应用
10.1 DLL概述动态链接库( Dynamic Link Library,缩写为 DLL)是编写
Windows应用程序的关键部件。它是一种程序模块,是实现
Windows应用程序代码重用与共享的重要手段。通过 DLL可以实现多个应用程序共享代码和资源。在使用 Windows操作系统的过程中,可以在系统目录中发现很多以 DLL为扩展名的文件,
事实上它是 Windows操作系统中非常重要的一部分。许多可视化开发工具(如 Visual Basic)并不支持 DLL的创建,而 Delphi
7.0在 DLL的使用方面有非常优秀的表现。本章将介绍动态链接库方面的内容,包括动态链接库的工作原理、编写、调用方式及利用 DLL实现窗体重用和数据共享,相信通过对本章的学习,你可以基本掌握 DLL的创建及应用。
10.1.1 DLL的基本概念它们分别对应的是设备驱动文件,系统文件和字体资源文件 ) 。 它和可执行文件 ( exe) 非常类似,区别在于 DLL中虽然包含了可执行代码却不能单独执行,
只能由 Windows应用程序直接或间接调用 。
2,静态链接和动态链接 (Static Linking and Dynamic Linking)
早期 DOS下的 C语言版本中提供了许多含有大量函数的头文件(,h文件),所有的标准库函数都存在某一个函数库中,这些函数的代码在编译后直接包含在应用程序中。但随着 Windows多任务环境的出现,函数库的方法显得过于累赘。应用程序为了完成诸如屏幕输出、消息处理、内存管理、对话框等操作,不得不拥有自己的函数,这样,Windows应用程序就变得非常庞大,而 Windows 的发展要求允许同时运行的几个程序共享一组函数的单一拷贝。
1,什么是动态链接库
DLL是一个可以被其他应用程序共享的程序模块,其中包含了一些可以被多个 Windows应用程序共享的代码,数据和资源 。 DLL主要的用途是使应用程序可以在运行时刻载入其中的代码,而不是在编译的时刻链接到应用程序中 。 动态链接库文件的扩展名一般是 dll,也有可能是 drv,sys和 fon(
10.1.1 DLL的基本概念图 10-1描述了系统在静态链接和动态链接时的调用过程,其中动态链接时生成的文件明显小于静态链接所生成的文件,这是使用 DLL的优势。
图 10-1 静态链接和动态链接在链接应用程序过程中,连接器从库文件中将这些被应用程序调用的函数代码添加到可执行文件中。所谓 静态链接 是指把要调用的函数或过程直接链接到可执行文件中,成为可执行文件的一部分。 动态链接 是相对于静态链接而言的,动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,
而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。
10.1.2 使用 DLL的优点 (1)
2,隐藏实现的细节有时希望隐藏 DLL中例程的实施细节。 事实上 DLL只提供函数的调用而不泄漏实现的源代码。如果希望应用程序访问 DLL,只要提供接口单元让其他人可以访问即可。 Delphi 7.0中的 Windows单元就是 Win32 DLL的接口单元,即 Windows.pas 文件。
1,多个应用程序共享代码、资源和数据使用 DLL主要是为了实现共享代码,DLL可以让在任何可以调用 DLL函数的 Windows应用程序之间共享代码。同时,DLL提供了共享资源的手段,
如位图、字体、图标等资源可以直接放在 DLL中,以便多个应用程序同时使用它们,从而节省了内存空间。
使用 DLL建立一个可执行文件时,DLL只是被映射到应用程序进程的地址空间中,不管多少程序使用 DLL,内存中都只有该 DLL的一个拷贝,而当没有程序使用它时,系统就将其移出内存。使用 DLL有许多优点,大体上说,
使用动态链接库可以共享代码和系统资源,可以隐藏实现代码的细节及拓展开发工具的功能,下面分别讨论这几方面的内容。
10.1.2 使用 DLL的优点 (2)
根据 DLL所完成的功能,把 DLL分为以下三类:
完成一般功能的 DLL;
用于数据交换的 DLL;
用于窗体重用的 DLL。
10.2 DLL的创建
3,拓展开发工具的功能
DLL还具有语言无关性,可以在 Delphi 7.0环境中创建被 C++,VB
或其他支持 DLL的语言使用 DLL;同样也可用其他语言创建被 Delphi 7.0
使用的 DLL。这样,如果一种语言存在不足,就可以通过访问另一种语言创建的 DLL来弥补,从而拓展了开发工具的功能。
10.2.1 DLL工程文件在 Delphi 7.0中,编写一个 DLL和编写一个一般的应用程序并没有太大的区别,真正的不同在它们的工程文件上,一般应用程序的工程文件的格式为:
program 工程名;
uses 子句;
程序体而 DLL工程文件的格式为:
library 工程名;
uses 子句;
exports 子句;
程序体说明两点,
( 1)一般应用程序的工程文件用 program作为关键字,而 DLL工程文件用
Library作为关键字。不同的关键字通知 Delphi 7.0 编译器生成不同的可执行文件,Program关键字生成,exe文件,而 Librarys 关键字生成的是,DLL文件。
( 2)在 DLL中供其他应用程序使用的函数和过程必须用 Exports进行输出。
10.2.2 DLL中的 Exports子句格式如下:
Exports
X1,X2,……,Xn;
其中 X1,X2可以是在 Exports子句之前已声明过的函数或过程,中间用逗号分隔。在 DLL的工程文件中,包含的过程或函数如果不在 Exports子句中列出,
则其他应用程序就不能够使用它们。 Exports子句可以出现在一个程序或库的声明部分,也可以出现在单元文件的 interface或 implementation中的任意位置,
出现的次数也没有限制。
在动态链接库的输出部分,用到了如下 3个标准指示。
( 1) Name
Name后面接一个字符串,作为该过程或函数的输出名,如:
Exports
Add name AddInt;
则其他应用程序将用 AddInt调用该过程或函数。
( 2) Index
Index指示为过程或函数分配一个顺序号,如果不使用 Index 编号,则
Delphi 7.0编译器将按照顺序进行分配,其数字范围是 1~32767,使用 Index可以加速调用过程或函数。
10.2.3 编写 DLL (1)
( 3) Resident
使用 Resident,则当 DLL载入时,特定的输出信息始终保持在内存中,
这样当其他应用程序调用该过程时,可以比利用名字扫描 DLL函数的入口降低时间的开销。
10.2.2 DLL中的 Exports子句
1,建立 DLL工程文件进入 Delphi 7.0开发环境中,单击“新建”,出现图 10-2时选择 DLL Wizard,进入代码资源管理器。
图 10-2 Delphi 7.0新建对话框动态链接库的源文件和程序文件并没有太大的区别,只是程序文件使用 Program,而动态链接库文件使用 Library作为系统保留字。可以在打开的资源管理器窗口中输入 【 例 10-1】 所示代码。
10.2.3 编写 DLL(2)
【 例 10-1】 MathDll.dpr具有整数相加和整数相减函数的 DLL。
library MathDll;
function Add(X,Y:Integer):Integer;stdcall;
begin
result:=X+Y;
end;
function Sub(X,Y,Integer):Integer;stdcall;
begin
result:=X-Y;
end;
exports
Add,Sub;
end.
10.2.3 编写 DLL(3)
2,编译 DLL工程文件保存工程文件 MathDll.dpr后,选择菜单中的 View项,选择 Project
Manager,鼠标右键选择 Mathdll.dll后单击 Compile,如图 10-3所示。
即可在相关目录下生成 MathDll.dll文件;或者直接选择运行菜单中的编译 MathDll.dll或按 Ctrl+ F9组合键。
图 10-3 Project Manager 窗口
10.2.4 DLL的调用要调用 DLL中的函数或过程,首先要将 DLL文件映射到调用进程的地址空间中,有两种方法可以实现这种映射,一种是在装入时动态链接,另一种是运行时动态链接,分别把它们称为静态调用和动态调用 。
1,静态调用( Load-time Dynamic Linking)
这种用法的前提是在编译之前已经明确知道要调用 DLL中的哪几个函数,编译时在目标文件中只保留必要的链接信息,而不含 DLL函数的代码;
当程序执行时,利用链接信息加载 DLL函数代码并在内存中将其链接入调用程序的执行空间中,其主要目的是便于代码共享。
2,动态调用( Run-time Dynamic Linking)
这种方式是指在编译之前并不知道将会调用哪些 DLL函数,完全是在运行过程中根据需要决定应调用哪些函数,并用 LoadLibrary和
GetProcAddress动态获得 DLL函数的入口地址。
10.3 DLL的静态调用方法
10.3.1 基本概念编写 DLL的目的是为了输出例程供其他程序调用。对于静态调用来说,
编译之前已经明确知道要调用 DLL中的哪几个函数,编译时在目标文件中只保留必要的链接信息,而不含 DLL函数的代码。使用这种方法,程序无法在运行时决定 DLL的调用,假如一个特定的 DLL在运行时无法加载,则应用程序将无法执行下去。
10.3.2 调用方法
【 例 10-2】 Dll的静态调用。
进入 Delphi 7.0默认的应用程序界面,在设计的应用程序窗体中添加如图 10-4所示组件。
选择查看菜单中的代码资源管理器,在保留字
Implementation行后添加如下代码:
function Add(x,y:Integer):Integer;stdcall;external
'mathdll.dll' name 'Add';
function Sub(x,y:Integer):Integer;stdcall;external
'mathdll.dll' name 'Sub';
图 10-4 DLL静态调用
10.3.2 调用方法其中保留字 Stdcall是一种调用约定,调用约定指定了当调用函数或过程时编译器传递参数的方式。主要有五种调用约定,Stdcall,Pascal、
Register,Cdecl 和 Safecall。
如果在 DLL中声明了某种全局变量,如 var s:byte。这样在 DLL中 s这个全局变量是可以正常使用的,但 s不能被调用程序使用,即 s不能作为全局变量传递给调用程序。不过在调用程序中声明的变量可以作为参数传递给 DLL。
在确定按钮控件的单击事件中添加以下代码:
procedure TForm1.Button1Click(Sender,TObject);
var x1,y1:Integer;
begin
x1:=strtoint(form1.Edit1.Text);
y1:=strtoint(form1.Edit2.Text);
form1.Edit3.Text:=inttostr(Add(x1,y1));
form1.Edit4.Text:=inttostr(Sub(x1,y1));
end;
10.4 DLL的动态调用方法
10.4.1 基本概念不难发现,在 Delphi 7.0中静态调用 DLL库是十分简单的事情,但有时候这不是最好的方法 。 假设某个 DLL中含有大量的输出函数和过程,而应用程序中有可能只需用其中的一部分例程 。 每次应用程序启动时,会浪费大量的内存空间去加载这个 DLL。 尤其是应用程序使用多个 DLL时更是如此 。 如果能够在应用程序需要的时候才载入 DLL,在不需要的时候又能够把它们调出内存,这种方式就叫 DLL的动态调用 。
10.4.2 动态调用使用的 API函数动态调用使用的 Windows API函数主要有 3个,即 LoadLibrary、
GetProcAddress和 FreeLibrary。
1,LoadLibrary函数语句格式为:
function LoadLibrary(LibFileName:PChar):Thandle;
10.4.2 动态调用使用的 API函数( 1)
LibFileName指定了要装载 DLL的文件名,如果 LibFileName没有包含一个路径,系统将按照如下顺序进行查找:
( 1)当前目录;
( 2) Windows目录;
( 3) Windows系统目录;
( 4)包含当前任务可执行文件的目录;
( 5)列在 PATH环境变量中的目录。
( 6)网络中的映像目录列表。
如果函数操作成功,则返回装载 DLL库的实例句柄,否则,返回一个错误代码,
错误代码的定义如表 10-1详见本教程 P182页所示。
2,GetProcAddress函数语句格式为:
function GetProcAddress(Module:Thandle;ProcName:PChar):TfarProc;
Module包含被调用的 DLL句柄,这个值由 LoadLibrary返回,ProcName
是指向含有函数名的以 nil结尾的字符串指针,或者可以是函数的次序值,
但大多数情况下,用函数名是一种更稳妥的选择,如果 GetProcAddress
执行成功,则返回 DLL中函数入口处的地址,否则返回 nil。
10.4.2 动态调用使用的 API函数( 2)
3,FreeLibrary函数语句格式为:
procedure FreeLibrary(Module:Thandle);
Module为 DLL库的句柄。这个值由 LoadLibrary返回。
由于 DLL在内存中已装载一次,因此调用 FreeLibrary首先使 DLL的引用计数减
1,如果计数减为 0则卸载该 DLL。
10.4.3 动态调用方法仍然列举进行整数相加的程序,不同的是,这次使用动态调用的方法。
【 例 10-3】 DLL的动态调用。
进入 Delphi 7.0后,设计如图 10-5所示的应用程序界面。
图 10-5 DLL的动态调用
10.4.3 动态调用方法( 1)
双击“确定”按钮,在代码资源管理器中添入如下代码:
procedure TForm1.Button1Click(Sender,TObject);
type
Tfunc=function(x,y:Integer):Integer;stdcall;
var x1,y1:Integer;
Th:Thandle;
Tf:Tfunc;
begin
Th:=LoadLibrary('mathdll.dll');
if Th>0 then
try
@Tf:=GetprocAddress(Th,'Add');
if @Tf <> nil then
begin
x1:=strtoint(form1.Edit1.Text);
y1:=strtoint(form1.Edit2.Text);
form1.Edit3.Text:=inttostr(Tf(x1,y1));
10.4.3 动态调用方法( 2)
end
else
showmessage(' mathdll.dll did not find,');
finally
FreeLibrary(Th);
end;
end;
end.
在 Delphi 7.0中异常包含了产生不正常事件的原因和位置信息,上述代码中的 try指明了需要保护的代码(从 try语句以下到 finally语句以上的一段代码)当这些受保护的代码产生异常后,应用程序直接执行 finally部分的语句,从而确保所分配内存资源的释放
10.5 利用 DLL实现窗体的重用在 DLL中除了编写函数和过程,还可以把一个完整的 Delphi 7.0窗体放在 DLL中,这个窗体可以是一个对话框或者其他任何形式的表单,它不但可以被 Delphi 7.0的其他应用程序所调用,也可以被其他开发环境或宏语言所使用。
创建一个令自己满意的通用窗体并希望在不同的应用程序中使用,特别是希望在非 Delphi 7.0应用程序中使用时,把窗体做到 DLL中是最合适的。一般窗体的 DLL需要 100KB左右的组件库开销,用户可以通过把几个窗体编译成一个 DLL来最小化这笔开销,DLL中的不同窗体可以共享组件库。
10.5.1 创建含有窗体的 DLL工程文件的一般步骤在 Delphi 7.0环境中创建含有窗体的 DLL步骤是这样的:
( 1)进入 Delphi 7.0环境,按自己的需要设计一个窗体;
( 2)编写一些用于输出的函数或过程,在这些函数或过程中,设计的窗体被实例化;
10.5.2 创建含有窗体的 DLL工程文件
( 3)打开相关单元文件,进行一些必要的修改,以适应生成 DLL
文件的需要;
( 4)编译生成的 DLL文件。
10.5.1 创建含有窗体的 DLL工程文件的一般步骤
【 例 10-4】 创建含有窗体的 DLL。
进入 Delphi 7.0环境,建立如图 10-6所示窗体,并且输入相关的事件代码。 图 10-6 含有 DLL的窗体
整个单元文件的代码如下所示:
unit DllFormUnit;
interface
uses
Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,
Forms,Dialogs,StdCtrls;
10.5.2 创建含有窗体的 DLL工程文件
type
TDllForm = class(TForm)
Label1,TLabel;
Label2,TLabel;
Edit1,TEdit;
procedure Edit1Change(Sender,TObject);
end;
var DllForm,TDllForm;
function UppToLow:boolean;stdcall;
implementation {$R *.dfm}
function UppToLow:boolean;
var DllForm:TDllForm;
begin
result:=false;
DllForm:=TDllForm.Create(Application);
DllForm.ShowModal ;
end;
procedure TDllForm.Edit1Change(Sender,TObject);
var tmpch:PChar;
10.5.2 创建含有窗体的 DLL工程文件
begin
tmpch:=stralloc(30);
strpcopy(tmpch,edit1.Text);
label1.Caption:=strcopy(tmpch,strupper(tmpch));
end; end.
打开相关的工程文件,将其中的关键字 program 改为 library。删除其中的从
begin开始到 end之间的程序段,并且添加一些必要的代码。整个工程文件如下所示。
library DllFrmPrj;
uses
Forms,
DllFormUnit1 in 'DllFormUnit1.pas' {DllForm};
{$R *.res}
exports
UppToLow;
end.
接下来的工作就是将该工程文件编译成 DLL文件。这个内容在 10.2节中已经作了介绍,在此就不再说明了。值得注意的是,还需要留意该 DLL文件具体存放的位置,以便调用 DLL的应用程序能够很快找到。
整个单元文件的代码如下所示:
unit Unit1;
interface
uses
Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,
Forms,Dialogs,StdCtrls,Buttons;
type
TMainForm = class(TForm)
ToDllFrm,TButton;
procedure ToDllFrmClick(Sender,TObject);
end;
var
MainForm,TMainForm;
function UppToLow:boolean;stdcall external 'd:\delphi\dll\DllFrmPrj.dll';
10.5.3 调用含有窗体的 DLL工程文件( 1)
【 例 10-5】 调用含有窗体的 DLL。
进入 Delphi 7.0环境,建立如图 10-7所示窗体,并且在,ToDllFrm”按钮中输入相关的事件代码。
图 10-7 调用 DLL窗体
10.5.3 调用含有窗体的 DLL工程文件( 2)
implementation
{$R *.dfm}
procedure TMainForm.ToDllFrmClick(Sender,TObject);
begin
UppToLow;
end;
end.
整个程序运行结果如图 10-8所示。
图 10-8 运行结果