Delphi 使用之dll文件生成与调用

时间:2024-01-10 18:52:20
DLL是Dynamic-Link Libraries(动态链接库)的缩写,库里面是一些可执行的模块以及资源(如位图、图标等)。可以认为DLL和EXE基本上是一回事,只是DLL不能直接执行,而必须由应用程序或者其他DLL调用。DLL为应用程序间的资源共享提供了方便,同时也是多语言混合编程的重要手段。由此可见学习使用DLL是Windows程序员必须掌握的一项重要技术。
1、DLL文件调用

在DELPHI中,有两种方法调用一个储存在DLL(动态链接库)中的函数和过程,即静态调用或者动态调用。

1)、 静态调用或显式装载

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

Function instring (sourcestr: Pchar ; check: char): integer; far; external 'demostr'

这种方式要在单元的interface 部分用external 指示字列出要从DLL中调用的例程。Far 指令表明可以被其他段(例如其他单元)调用的子例程。所有在单元接口中声明的子例程在缺省情况下都是Far类型的,其相反的指令是near。

如果external 后什么也不跟,必须用 {$ L } 编译指令预先指定一个DLL名字,如:

{ $ L Mydlls.dll }
Procedure setstring(var str: string) ;
stdcall ; external

但是使用静态调用方法时,程序无法在运行时间里决定DLL的调用。在DELPHI中使用DLL时,例程的标识符必须与DLL中相应输出例程的标识符完全一致(尽管DELPHI本身大小写不敏感)。使用外部声明的缺点是程序启动时如果找不到mydll.dll将无法运行,即使没有调用其中的模块。 动态加载的方法可以避免这种情况。

2)、 动态调用或隐式装入

使用WINDOWS API 函数 Loadlibrary 和GetprocAddress可以实现在运行时间里的动态装载DLL,并调用其中的过程。将DLL调入内存并获得指向函数或过程的指针,执行完模块后释放内存。除了节约内存外,这种方法的一个很大的优点是能处理找不到dll或者在装入过程中出错的情况。这样即使某个dll有问题,应用程序的其他部分仍然能够正常运行。动态加载的例子如下:

Type
TMyProc=Procedure (Param:Pchar ) ;Stdcall; Var MyProc: TMyproc;
MyHandle:THandle;
MyHandle:=LoadLibrary ('Mydll') ; If MyHandle<= then
   Raise Exception.Create( '动态链接库调用失败,错误代码是:'+Inttostr(Getlasterror))
else
@MyProc:=GetProcAddress(MyHandle,'demoproc'); if not Assigned(MyProc) then
Raise Exception.Create('GetProcAddress 调用失败,错误代码是:'+inttostr(getlasterror))
else MyProc(Pchar('a string'));
Freelibrary(Myhandle); // 卸载DLL

3)、 调用方式

通过过程、函数名;

通过过程、函数别名;

通过过程、函数的顺序号

举例如下:在MYDLL.DLL中有两个函数和一个过程,则其外部声明可以写成:

例:

Function Getstring : string ; stdcall ; external 'Mydlls.dll' name 'Mygetstr' //name 子句指定函数名Getstring 改为Mygetstr,当程序调用这个例程时,使用Mygetstr这个名字;

Function Getstring : string ; stdcall ; external 'Mydlls.dll' index  //Index 子句通过索引号引入例程可以减少DLL的加载时间。

4)、 调用约定

调用约定,是指调用例程时参数的传递顺序。DELPHI中DLL支持的调用约定有:

调用约定 参数传递顺序
Register  从左到右
Pascal  从左到右
Stdcall  从右到左
Cdecl  从右到左
Safecall  从右到左

使用Stdcall 方式,能保证不同语言写的DLL的兼容性,同时它也是WINDOWS API的约定方式; Delphi 3.0、4.0的默认调用方式为Register; Cdecl是采用 C/C++的调用约定,适用于DLL是由C++语言编写的; Safecall 是适合于声明OLE对象中的方法。

5)、 DLL中的变量和段

一个DLL声明的任何变量都为自己私有 ,调用它的模块不能直接使用它定义的变量。要使用时必须通过过程或函数界面才能完成,对DLL来说,它永远都没有机会使用调用它的模块中的声明的变量。一个DLL没有自己的SS(堆栈段),它使用调用它的应用程序的堆栈。因此在DLL中的过程、函数不要假定DS=SS(DS为数据段)。

2、创建DLL

  用Delphi创建一个DLL是十分简单的,首先需要新建一个DLL的Porject:选择File/New/Others...,如下:

Delphi 使用之dll文件生成与调用

选择“Dll Wizard”,点击 OK 按钮。

内容实例如下:

library DemoSvr;

{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. } uses SysUtils,Classes;
function Test1(a,b:integer):integer;
begin
Result:=a+b;
end;
exports
Test1 index ;
begin
end.

在这个DLL里我们声明了一个加法函数,然后用exports语句输出它,只有被输出的函数或过程能被其他程序调用。

调试(编译/Ctrl+F9)成功以后 会生成一个DemoSvr.dll 文件。可以设置编译的路径,选择"Project/Options..."菜单:

Delphi 使用之dll文件生成与调用

在DLL项目中,可以指定一个宿主程序来调试,具体方法为选择 "Run/Parameters..."菜单:

Delphi 使用之dll文件生成与调用

另外,可以设置跟踪调试:

Delphi 使用之dll文件生成与调用

exports语句的语法是:

函数名 [index <n>],

index <n>是为函数手工指定索引号,以便其他程序确定函数地址;也可以不指定,如果没有使用Index关键字,Delphi将按照exports后的顺序从1开始自动分配索引号。现在我们可以调用这个DLL了,下面给出一个实例,运行后form1的标题将变成“1+2=3”:

声明部分:function Test1(a,b:integer):integer;external 'DemoSvr'; //注意此处是大小写敏感的。
运行部分:form1.caption:='1+2='+inttostr(test1(,));
3、使用DLL的两个技巧

(1)把现有的项目改成DLL
  学会制作DLL以前,大多数程序员手中都积攒下来不少已经完成了的项目,如果现在需要把这些项目做成DLL而不是可执行文件,重新写一遍显然是没有必要的,只要按照下面的步骤对已有的项目文件进行修改就可以了:
  ① 打开项目文件(.DPR),删除单元底部begin和end.之间的所有语句(一般情况下这些语句是由Delphi自动生成的)。如果项目中没有用到Form,则从uses子句中删除表单单元(Form),然后转到第③步。
  ② 对项目进行修改,令除Main Form之外的所有Form都是动态生成的,这样我们只要在DLL输出的一个函数或者过程中生成Main Form,即可调用执行整个项目。我们假设Main Form的名字是MyMainForm,项目的名字是MyDll,现在在单元底部的begin语句之前加入一个过程,过程的名字为RunMyDll,这个过程将动态生成Main Form,从而运行整个项目。RunMyDll的写法如下:

procedure InitDll2;
begin
  Application.CreateForm(TMyMainForm, MyMainForm);
  MyMainForm.Show; //如果MyMainForm不可视则需要这一句.
end;

  ③ 如果想要输出其他函数或者过程,而原来的项目中没有,则可以在单元底部的begin语句之前加入这些代码。

  ④ 在单元底部的begin语句之前加入一个exports小节,然后写出所有想要输出的函数或过程的名字(最好指定索引号)。注意如果执行了第②步,一定要输出RunMyDll过程。
  ⑤ 将项目文件顶部的保留字program改为library。
  ⑥ 编译。
  现在就可以在其他程序中调用本项目中的函数和过程了,只要执行RunMyDll就可以执行这个项目,和执行原来的可执行文件一模一样。

(2)创建一个引入文件
  如果DLL比较复杂,则为它的声明专门创建一个引入程序单元将是十分有意义的,并且会使这个DLL变得更加容易维护。引入单元的格式如下:

  unit MyImport; {Import unit for MyDll.Dll}
  interface
  procedure RunMyDll;
  implementation
  procedure RunMyDll;external 'MyDll' index ;
  end.

这样以后想要使用MyDll中的例程时,只要简单的在程序模块中的uses子句中加上MyImport即可。