首先说明一下:同一个动态库(DLL)被多个的程序加载的话,那么将会在每次加载的时候都会重新分配新的独立的内存空间,绝对不是共用一个,所以当一个DLL被多次加载的时候,其会在内存中“复制”多份,不会互相之间 产生影响。

  加载DLL有两种方式:隐式和显式。下面就以刚创建的DLL为例,来介绍两种方式

一、隐式

  本章创建的第一个 D L L包含一个接口单元。下面就用该接口单元来隐式链接 DLL。这个工程的主窗体上有一个TMadkEdit、一个TButton和九个TLabel。

  在这个应用程序里,用户输入美分的数目,然后单击按钮,标签上将按条目显示其结果。这结果是来源于PenniesLib.dll中引出的例程 PenniesToCoins()。

  主窗体是在MainFrm.pas单元里定义的,代码

unit MainRrm;

interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Mask;
type
TMainForm = class(TForm)
lblTotal: TLabel;
lblQlbl: TLabel;
lblDlbl: TLabel;
lblNlbl: TLabel;
lblPlbl: TLabel;
lblQuarters: TLabel;
lblDimes: TLabel;
lblNickels: TLabel;
lblPennies: TLabel;
btnMakeChange: TButton;
meTotalPennies: TMaskEdit;
procedure btnMakeChangeClick(sender: TObject);
end; var MainForm: TMainForm; implementation
uses PenniesInt;
{R *.DFM} procedure TMainForm.btnMakeChangeClick(Sender: TObject);
var
CoinsRec: TCoinsRec;
TotPennies: word;
begin
{call the DLL function to determine the minimum coins required
for the amount of pennies specified}
TotPennies:= PenniesToCoins(StrToInt(meTotalPennies.Text), @CoinsRec);
with CoinsRec do
begin
{Now display the coin information}
lblQuarters.Caption:= IntToStr(Quarters);
lblDimes.Caption:= IntToStr(Dimes);
lblNickels.Caption:= IntToStr(Nickels);
lblPennies.Caption:= IntToStr(Pennies);
end;
end; end.

  注意MainFrm.pas使用了接口单元PenniesInt。PenniesInt.pas包括了PenniesLib.dpr中的函数的外部声明。当应用程序运行时,Win 32系统会自动调入 PenniesLin.dll,并将其映射到该应用程序的进程地址空间

  import 接口单元其实是可选的,可以不用 PenniesInt单元,而在 MainFrm.pas 的 implementation部分外部声明 PenniesToCoins() 函数即可,代码如下

implementation

function PenniesToCoins(TotPennies: word; ChangeRec: PChangeRec): Word; stdcall; external 'PENNIESLIB.DLL';

  不过,在MainFrm.pas 还要再次定义 PChangeRec 和TChangeRec,要不然也可以使用编译指令 PENNIESLIB 编译你的应用程序。在只需要访问DLL中的少数例程时,这种技术比较合适。不过,大多数情况下是既要访问DLL中的例程,又要引用 DLL中定义的数据类型,这时候最好使用接口单元

  注意:有些时候,需要调用其他软件厂商的DLL,这时候就不会有 Pascal的接口单元,不过,也许有 C/C++的引入库。如果是这样的话,就只要显将该库转换为等效的Pascal接口单元。

二、显式调用

  尽管隐式调用很方便,但不是最理想的方法。假使有一个包含多个例程的DLL,可能这些例程却用不着,这样的话,调入该DLL显然浪费了内存。尤其是一个应用程序需要使用多个DLL时,这就更浪费了。另一种情况是,一个多版本的标准函数,如一组打印驱动程序,分别是由多个DLL来实现的。这种情况下,如果能需要哪一个版本的就调用其DLL,这最好不过了。这种方式就是显示调用。

unit MainFrm;

interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls; type
TShowCalendar = function(AHandle: THandle; ACaption: String): TDateTime; Stdcall; EDLLLoadError = class(Exception); TMainForm = class(TForm)
lblDate: TLabel;
btnGetCalendar: TButton;
procedure btnGetCalendarClick(Sender: TOnject);
end; var
MainForm: TMainForm; implementation
{$R *.DFM} procedure TMainForm.btnGetCalendarClick(Sender: TObject);
var
LibHandle: THandle; //!!!!!
ShowCalendar: TShowCalendar; //!!!!!!
begin
LibHandle:= LoadLibrary('CALENDARLIB.DLL'); //!!!!!!
try
if LibHandle = 0 then
raise EDLLLoadError.Create('Unable to load DLL'); @ShowCalendar:= GetProcAddress(LibHandle, 'ShowCalendar'); if not(@ShowCalendar = nil) then
lblData.Caption:= DateToStr(ShowCalendar(Application.Handle, Caption))
else
RaiseLastWin32Error;
finally
FreeLibrary(LibHandle);
end;
end; end.

  上面这个单元首先定义了一个过程数据类型 TShowCalendar,该类型就是 CalendarLib.dll 中的函数类型。接着,又声明一个特殊的异常类,当调用失败时,就会引发这个异常。在 btnGetCalendarClick() 事件处理过程中,使用了三个Win32 API函数:LoadLibrary()、FreeLibrary()、GetProcAddress()。

  LoadLibrary()声明如下

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

  上述函数调入有 lpLibFileName参数指定的DLL模块,并将其映射到调用进程的地址空间。如果调用成功,函数将返回该模块的句柄;若失败,返回值为0,并触发一异常。你可以查阅在线帮助中 LoadLibrry() 函数的详细说明以及可能返回的错误值

  FreeLibrary()声明如下

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

  FreeLibrary()函数减少LibModule指定的库的实例计数。当该DLL的实例计数是零时,调用的DLL就会被释放。实例计数记录使用这个DLL的任务数

  GetProcAddress()是这样定义的

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

  GetProcAddress() 返回的是一个函数在模块中的地址,其中由 hModule参数指定模块。hModule是从 LoadLibrary() 函数返回的结果THandle。如果 GetProcAddress()调用失败,则返回 nil。你只有调用GetLastError()才能获得详细的错误信息。

  在处理 Button1 的OnClick事件处理过程中,首先调用LoadLibrary()去调用CALDLL。如果失败,则触发异常。如果成功,调用 GetProcAddress()来获得函数 ShowCalendar()的地址。在变量ShowCalendar 前加上取址运算符@,这样就不会在编译时出现类型转换错误。得到函数 ShowCalendar()的地址后,就可以按 TShowCalendar定义的使用它了。最后不再使用时,一定要在 finally 块中调用FreeLibrary() 来释放它。

  你也许明白,每次调用该函数时, DLL既要调入,又要释放。如果在应用程序运行时,只需要调用一次,显式调用的优势就很明显,它能够有效地减少内存的消耗。但是,如果该函数要被频繁地调用,那么频繁地调入和释放会增加系统负担。

Delphi之DLL知识学习5---在Delphi应用程序中使用DLL的更多相关文章

  1. Delphi for iOS开发指南(8):在iOS应用程序中使用Tab组件来显示分页

    Delphi for iOS开发指南(8):在iOS应用程序中使用Tab组件来显示分页 在FireMonkey iOS应用程序中的Tab Tab由FMX.TabControl.TTabControl定 ...

  2. Delphi for iOS开发指南(7):在iOS应用程序中使用WebBrowser组件

    Delphi for iOS开发指南(7):在iOS应用程序中使用WebBrowser组件 在FireMonkey iOS应用程序中使用WebBrowser 在iOS平台上,FireMonkey使用T ...

  3. Delphi for iOS开发指南(6):在iOS应用程序中使用ComboBox组件来从列表中选择某一项

    http://blog.csdn.net/delphiteacher/article/details/8924110 Delphi for iOS开发指南(6):在iOS应用程序中使用ComboBox ...

  4. Delphi之DLL知识学习4---创建DLL

    下面是在Delphi中创建一个DLL的全过程,你将看到怎样创建一个接口单元,使之可以被其他的应用程序访问.并且将学会怎么把Delphi的窗体加入DLL中. 一.数美分:一个简单的DLL 下面是包含一个 ...

  5. Delphi之DLL知识学习3---为什么要使用DLL

    使用DLL有若干理由,其中有一些前面提到过的.大体说来,使用动态链接库可以共享代码.系统资源,可以隐藏实现的代码或底层的系统例程.设计自定义控件 一.共享代码.资源和数据 前面已经提到,共享代码是创建 ...

  6. Delphi之DLL知识学习2---静态链接和动态链接

    静态连接 静态链接是指Delphi 编译器把要调用的函数和过程编译成可执行代码.函数的代码可存留在应用程序的 .dpr文件或一单元中.当链接用户的应用程序时,这些函数与过程便成为最终的可执行文件的一部 ...

  7. Delphi之DLL知识学习1---什么是DLL

    DLL(动态链接库)是程序模块,它包括代码.数据或资源,能够被其他的Windows 应用程序共享.DLL的主要特点之一是应用程序可以在运行时调入代码执行,而不是在编译时链接代码,因此,多个应用程序可以 ...

  8. 【Delphi内联汇编学习1】Delphi与汇编

    我一直认为Delphi功能与C++相比毫不逊色,提供了丰富的控件和类.全部API以及嵌入的汇编.最近小弟在把C版的Huffman压缩改用Delphi写时,顺便“研究”了一下Delphi的位操作和嵌入式 ...

  9. X86逆向13:向程序中插入Dll

    本章我们将学习Dll的注入技巧,我们将把一个动态链接库永久的插入到目标程序中,让程序在运行后直接执行这个Dll文件,这一章的内容也可以看作是第八课的加强篇,第八课中我们向程序中插入了一个弹窗,有木有发 ...

随机推荐

  1. rpm包制作(待实验)

    作者:firefoxbug 时间:July 18, 2014 rpm包命名规范 对于rpm包的命名符合如下规范. %{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm N ...

  2. SSRS报表参数设置

    一.日期时间类型的参数注意事项: 关于数据类型的选择:(只有数据类型设置为日期/时间格式,在查询的时候才会显示日期控件,提示信息一般改成汉字) 指定默认值:指定开始日期为前10天,

  3. SQL Server中的索引

    1 SQL Server中的索引 索引是与表或视图关联的磁盘上结构,可以加快从表或视图中检索行的速度.索引包含由表或视图中的一列或多列生成的键.这些键存储在一个结构(B 树)中,使 SQL Serve ...

  4. centos7时间同步和时区设置

    centos7时间同步和时区设置 安装ntp服务的软件包 sudo yum install ntp 将ntp服务设置为缺省启动 systemctl enable ntpd 修改启动参数,增加-g -x ...

  5. Light OJ 1140

    数位dp,需要记录前导0. 数位dp中需要注意统计0,00,000……这些数字. 数位dp的写法可以分为两类.由于我们通常采用记忆化搜索的方式进行dp,所以我们有一个记忆化数组. 一种是记忆化数组的意 ...

  6. 自动编译和提交脚本(结合svn和visual studio)

    @echo 更新代码开始----------------- TortoiseProc.exe /command:update /path:"D:\work\mmsanguo_publish_ ...

  7. javascript 搜索并高亮显示

    2015年12月22日 15:45:08 星期二 情景: 用来筛选列表中的数据, 由于单条数据很简短, 没有用php+mysql去实现筛选功能, 只用javascript进行筛选, 匹配的高亮, 或者 ...

  8. ACM/ICPC 之 昂贵的聘礼-最短路解法(POJ1062)

    //转移为最短路问题,枚举必经每一个不小于酋长等级的人的最短路 //Time:16Ms Memory:208K #include<iostream> #include<cstring ...

  9. Win7下同时使用有线和无线时的优先级设置

    终于找到这个问题的解决方案了!!!!我是通过方法1改跃点数实现的,方法2无效. http://linshengling.blog.163.com/blog/static/114651912012102 ...

  10. 【转】Android Support v4、v7、v13的区别和应用场景

    google提供了Android Support Library package 系列的包来保证来高版本sdk开发的向下兼容性,即我们用4.x开发时,在1.6等版本上,可以使用高版本的有些特性,如fr ...