Delphi 动态链接库(一)

来源:互联网 发布:中国教育数据统计网 编辑:程序博客网 时间:2024/06/04 19:45

======================================================
注:本文源代码点此下载
======================================================

在下面的程序中我们把一个字符串操作的函数储存到一个dlls中,以便需要的时候调用它。应该注意的一点是:为了保证这个函数可以被其它语言编写的程序所调用,作为参数传递的字符串应该是无结束符的字符数组类型(即pchar类型),而不是object pascal的带结束符的srting类型。程序清单如下:

library example;

uses

sysutils,

classes;

{返回字符在字符串中的位置}

function instr(sourcestr: pchar;ch: char): integer; export;

var

len,i: integer;

begin

len := strlen(sourcestr);

for i := 0 to len-1 do

if sourcestr[i] = ch then

begin

result := i;

exit;

end;

result := -1;

end;

exports

instr index 1 name 'myinstr' resident;

begin

end.

10.2.2 调用dlls

有两种方法可用于调用一个储存在dlls中的过程。

1.静态调用或显示装载

使用一个外部声明子句,使dlls在应用程序开始执行前即被装入。例如:

function instr(sourcestr : pchar;check : char); integer; far; external'usestr';

使用这种方法,程序无法在运行时间里决定dlls的调用。假如一个特定的dlls在运行时无法使用,则应用程序将无法执行。

2.动态调用或隐式装载

使用windows api函数loadlibray和getprocaddress可以实现在运行时间里动态装载dlls并调用其中的过程。

若程序只在其中的一部分调用dlls的过程,或者程序使用哪个dlls, 调用其中的哪个过程需要根据程序运行的实际状态来判断,那么使用动态调用就是一个很好的选择。

使用动态调用,即使装载一个dlls失败了,程序仍能继续运行。

10.2.3 静态调用

在静态调用一个dlls中的过程或函数时,external指示增加到过程或函数的声明语句中。被调用的过程或函数必须采用远调用模式。这可以使用far过程指示或一个{$f +}编译指示。

delphi全部支持传统windows动态链接库编程中的三种调用方式,它们是:

● 通过过程/函数名

● 通过过程/函数的别名

● 通过过程/函数的顺序号

通过过程或函数的别名调用,给用户编程提供了灵活性,而通过顺序号(index)调用可以提高相应dll的装载速度。

10.2.4 动态调用

10.2.4.1 动态调用中的api函数

动态调用中使用的windows api函数主要有三个,即:loadlibrary,getprocaddress和freelibrary。

1.loadlibrary: 把指定库模块装入内存

语法为:

function loadlibrary(libfilename: pchar): thandle;

libfilename指定了要装载dlls的文件名,如果libfilename没有包含一个路径,则windows按下述顺序进行查找:

(1)当前目录;

(2)windows目录(包含win.com的目录)。函数getwindowdirectory返回这一目录的路径;

(3)windows系统目录(包含系统文件如gdi.exe的目录)。函数getsystemdirectory返回这一目录的路径;

(4)包含当前任务可执行文件的目录。利用函数getmodulefilename可以返回这一目录的路径;

(5)列在path环境变量中的目录;

(6)网络的映象目录列表。

如果函数执行成功,则返回装载库模块的实例句柄。否则,返回一个小于hinstance_error的错误代码。错误代码的意义如下表:

表10.2 loadlibrary返回错误代码的意义

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

错误代码意义

——————————————————————————————————————

0 系统内存不够,可执行文件被破坏或调用非法

2 文件没有被发现

3 路径没有被发现

5 企图动态链接一个任务或者有一个共享或网络保护错

6 库需要为每个任务建立分离的数据段

8 没有足够的内存启动应用程序

10 windows版本不正确

11 可执行文件非法。或者不是windows应用程序,或者在.exe映

像中有错误

12 应用程序为一个不同的操作系统设计(如os/2程序)

13 应用程序为ms dos4.0设计

14 可执行文件的类型不知道

15 试图装载一个实模式应用程序(为早期windows版本设计)

16 试图装载包含可写的多个数据段的可执行文件的第二个实例

19 试图装载一个压缩的可执行文件。文件必须被解压后才能被装裁

20 动态链接库文件非法

21 应用程序需要32位扩展

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

假如在应用程序用loadlibrary调用某一模块前,其它应用程序已把该模块装入内存,则loadlibrary并不会装载该模块的另一实例,而是使该模块的“引用计数”加1。

2.getprocaddress:捡取给定模块中函数的地址

语法为:

function getprocaddress(module: thandle; procname: pchar): tfarproc;

module包含被调用的函数库模块的句柄,这个值由loadlibrary返回。如果把module设置为nil,则表示要引用当前模块。

procname是指向含有函数名的以nil结尾的字符串的指针,或者也可以是函数的次序值。如果procname参数是次序值,则如果该次序值的函数在模块中并不存在时,getprocaddress仍返回一个非nil的值。这将引起混乱。因此大部分情况下用函数名是一种更好的选择。如果用函数名,则函数名的拼写必须与动态链接库文件exports节中的对应拼写相一致。

如果getprocaddress执行成功,则返回模块中函数入口处的地址,否则返回nil。

3.freelibrary:从内存中移出库模块

语法为:

procedure freelibrary(module : thandle);

module为库模块的句柄。这个值由loadlibrary返回。

由于库模块在内存中只装载一次,因而调用freelibrary首先使库模块的引用计数减一。如果引用计数减为0,则卸出该模块。

每调用一次loadlibrary就应调用一次freelibray,以保证不会有多余的库模块在应用程序结束后仍留在内存中。

10.2.4.2 动态调用举例

对于动态调用,我们举了如下的一个简单例子。系统一共包含两个编辑框。在第一个编辑框中输入一个字符串,而后在第二个编辑框中输入字符。如果该字符包含在第一个编辑框的字符串中,则标签框显示信息:“位于第n位。”,否则显示信息:“不包含这个字符。”。如图是程序的运行界面。

输入检查功能的实现在edit2的onkeypress事件处理过程中,程序清单如下。

procedure tform1.edit2keypress(sender: tobject; var key: char);

var

order: integer;

txt: pchar;

pfunc: tfarproc;

moudle: thandle;

begin

moudle := loadlibrary('c:\dlls\example.dll');

if moudle > 32 then

begin

edit2.text := '';

pfunc := getprocaddress(moudle,'instr');

txt := stralloc(80);

txt := strpcopy(txt,edit1.text);

order := tinstr(pfunc)(txt,key);

if order = -1 then

label1.caption := '不包含这个字符 '

else

label1.caption := '位于第'+inttostr(order+1)+'位';

end;

freelibrary(moudle);

end;

在利用getprocaddess返回的函数指针时,必须进行强制类型转换:

order := tinstr(pfunc)(text,key);

tinstr是一个定义好了的函数类型:

type

tinstr = function(source: pchar;check: char): integer;

10.3 利用dlls实现数据传输

10.3.1 dlls中的全局内存

windows规定:dlls并不拥有它打开的任何文件或它分配的任何全局内存块。这些对象由直接或间接调用dlls的应用程序拥有。这样,当应用程序中止时,它拥有的打开的文件自动关闭,它拥有的全局内存块自动释放。这就意味着保存在dlls全局变量中的文件和全局内存块变量在dlls没有被通知的情况下就变为非法。这将给其它使用该dlls的应用程序造成困难。

为了避免出现这种情况,文件和全局内存块句柄不应作为dlls的全局变量,而是作为dlls中过程或函数的参数传递给dlls使用。调用dlls的应用程序应该负责对它们的维护。

但在特定情况下,dlls也可以拥有自己的全局内存块。这些内存块必须用gmem_ddeshare属性进行分配。这样的内存块直到被dlls显示释放或dlls退出时都保持有效。

由dlls管理的全局内存块是应用程序间进行数据传输的又一途径,下面我们将专门讨论这一问题。

10.3.2 利用dlls实现应用程序间的数据传输

利用dlls实现应用程序间的数据传输的步骤为:

1. 编写一个dlls程序,其中拥有一个用gmem_ddeshare属性分配的全局内存块;

2. 服务器程序调用dlls,向全局内存块写入数据;

3. 客户程序调用dlls,从全局内存块读取数据。

10.3.2.1 用于实现数据传输的dlls的编写

用于实现数据传输的dlls与一般dlls的编写基本相同,其中特别的地方是:

1. 定义一个全局变量句柄:

var

hmem: thandle;

2. 定义一个过程,返回该全局变量的句柄。该过程要包含在exports子句中。如:

function getglobalmem: thandle; export;

begin

result := hmem;

end;

3. 在初始化代码中分配全局内存块:

程序清单如下:

begin

hmem := globalalloc(gmem_moveable and gmem_ddeshare,num);

if hmem = 0 then

messagedlg('could not allocate memory',mtwarning,[mbok],0);

end.

num是一个预定义的常数。

windows api函数globalalloc用于从全局内存堆中分配一块内存,并返回该内存块的句柄。该函数包括两个参数,第一个参数用于设置内存块的分配标志。可以使用的分配标志如下表所示。

表10.3 全局内存块的分配标志

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

标 志 意 义

—————————————————————————————————

gmem_ddeshare 分配可由应用程序共享的内存

gmem_discardable 分配可抛弃的内存(只与gmem_moveable连用)

gmem_fixed 分配固定内存

gmem_moveable 分配可移动的内存

gmem_nocompact 该全局堆中的内存不能被压缩或抛弃

gmem_nodiscard 该全局堆中的内存不能被抛弃

gmem_not_banked 分配不能被分段的内存

gmem_notify 通知功能。当该内存被抛弃时调用globalnotify函数

gmem_zeroinit 将所分配内存块的内容初始化为零

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

有两个预定义的常用组合是:

ghnd = gmem_moveable and gmem_zeroinit

gptk = gmem_fixed and gmem_zeroinit

第二个参数用于设置欲分配的字节数。分配的字节数必须是32的倍数,因而实际分配的字节数可能比所设置的要大。

由于用gmem_ddeshare分配的内存在分配内存的模块终止时自动抛弃,因而不必调用globalfree显式释放内存。


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/
原创粉丝点击