Delphi制作DLL (二)

第三章 在Delphi中静态调用DLL­

­

调用一个DLL比写一个DLL要容易一些。首先给大家介绍的是静态调用方法,稍后将介绍动态调用方法,并就两种方法做一个比较。同样的,我们先举一个静态调用的例子。 ­

­

unit Unit1; ­

­

interface ­

­

uses ­

Windows, Messages, SysUtils, Classes, Graphics, ­

Controls, Forms, Dialogs, StdCtrls; ­

­

type ­

TForm1 = class(TForm) ­

Edit1: TEdit; ­

Button1: TButton; ­

procedure Button1Click(Sender: TObject); ­

private ­

{ Private declarations } ­

public ­

{ Public declarations } ­

end; ­

­

var ­

Form1: TForm1; ­

­

implementation ­

­

{$R *.DFM} ­

­

//本行以下代码为我们真正动手写的代码 ­

­

function TestDll(i:integer):integer;stdcall; ­

external ’Delphi.dll’; ­

­

procedure TForm1.Button1Click(Sender: TObject); ­

begin ­

Edit1.Text:=IntToStr(TestDll(1)); ­

end; ­

­

end. ­

­

上面的例子中我们在窗体上放置了一个编辑框(Edit)和一个按钮(Button),并且书写了很少的代码来测试我们刚刚编写的Delphi.dll。大家可以看到我们唯一做的工作是将TestDll函数的说明部分放在了implementation中,并且用external语句指定了Delphi.dll的位置。(本例中调用程序和Delphi.dll在同一个目录中。)让人兴奋的是,我们自己编写的TestDll函数很快被Delphi认出来了。您可做这样一个实验:输入“TestDll(”,很快Delphi就会用fly-by提示条提示您应该输入的参数是什么,就像我们使用Delphi中定义的其他函数一样简单。注意事项有以 ­

下一些: ­

­

一、调用参数用stdcall。 ­

和前面提到的一样,当引用DLL中的函数和过程时也要使用stdcall参数,原因和前面提到的一样。 ­

­

二、用external语句指定被调用的DLL文件的路径和名称。 ­

正如大家看到的,我们在external语句中指定了所要调用的DLL文件的名称。没有写路径是因为该DLL文件和调用它的主程序在同一目录下。如果该DLL文件在C:/,则我们可将上面的引用语句写为external ’C:/Delphi.dll’。注意文件的后缀.dll必须写上。 ­

­

三、不能从DLL中调用全局变量。 ­

如果我们在DLL中声明了某种全局变量,如:var s:byte 。这样在DLL中s这个全局变量是可以正常使用的,但s不能被调用程序使用,既s不能作为全局变量传递给调用程序。不过在调用程序中声明的变量可以作为参数传递给DLL。 ­

­

四、被调用的DLL必须存在。 ­

这一点很重要,使用静态调用方法时要求所调用的DLL文件以及要调用的函数或过程等等必须存在。如果不存在或指定的路径和文件名不正确的话,运行主程序时系统会提示“启动程序时出错”或“找不到*.dll文件”等运行错误。 ­

­

第四章 在Delphi中动态调用DLL­

­

动态调用DLL相对复杂很多,但非常灵活。为了全面的说明该问题,这次我们举一个调用由C++编写的DLL的例子。首先在C++中编译下面的DLL源程序。 ­

­

#include ­

­

extern ”C” _declspec(dllexport) ­

int WINAPI TestC(int i) ­

{ ­

return i; ­

} ­

­

编译后生成一个DLL文件,在这里我们称该文件为Cpp.dll,该DLL中只有一个返回整数类型的函数TestC。为了方便说明,我们仍然引用上面的调用程序,只是将原来的Button1Click过程中的语句用下面的代码替换掉了。 ­

­

procedure TForm1.Button1Click(Sender: TObject); ­

type ­

TIntFunc=function(i:integer):integer;stdcall; ­

var ­

Th:Thandle; ­

Tf:TIntFunc; ­

Tp:TFarProc; ­

begin ­

Th:=LoadLibrary(’Cpp.dll’); {装载DLL} ­

if Th>0 then ­

try ­

Tp:=GetProcAddress(Th,PChar(’TestC’)); ­

if Tp<>nil ­

then begin ­

Tf:=TIntFunc(Tp); ­

Edit1.Text:=IntToStr(Tf(1)); {调用TestC函数} ­

end ­

else ­

ShowMessage(’TestC函数没有找到’); ­

finally ­

FreeLibrary(Th); {释放DLL} ­

end ­

else ­

ShowMessage(’Cpp.dll没有找到’); ­

end; ­

­

大家已经看到了,这种动态调用技术很复杂,但只要修改参数,如修改LoadLibrary(’Cpp.dll’)中的DLL名称为’Delphi.dll’就可动态更改所调用的DLL。 ­

­

一、定义所要调用的函数或过程的类型。 ­

在上面的代码中我们定义了一个TIntFunc类型,这是对应我们将要调用的函数TestC的。在其他调用情况下也要做同样的定义工作。并且也要加上stdcall调用参数。 ­

­

二、释放所调用的DLL。 ­

我们用LoadLibrary动态的调用了一个DLL,但要记住必须在使用完后手动地用FreeLibrary将该DLL释放掉,否则该DLL将一直占用内存直到您退出Windows或关机为止。 ­

­

现在我们来评价一下两种调用DLL的方法的优缺点。静态方法实现简单,易于掌握并且一般来说稍微快一点,也更加安全可靠一些;但是静态方法不能灵活地在运行时装卸所需的DLL,而是在主程序开始运行时就装载指定的DLL直到程序结束时才释放该DLL,另外只有基于编译器和链接器的系统(如Delphi)才可以使用该方法。动态方法较好地解决了静态方法中存在的不足,可以方便地访问DLL中的函数和过程,甚至一些老版本DLL中新添加的函数或过程;但动态方法难以完全掌握,使用时因为不同的函数或过程要定义很多很复杂的类型和调用方法。对于初学者,笔者建议您使用静态方法,待熟练后再使用动态调用方法。 ­

­

第五章 使用DLL的实用技巧­

­

一、编写技巧。 ­

1 、为了保证DLL的正确性,可先编写成普通的应用程序的一部分,调试无误后再从主程序中分离出来,编译成DLL。 ­

­

2 、为了保证DLL的通用性,应该在自己编写的DLL中杜绝出现可视化控件的名称,如:Edit1.Text中的Edit1名称;或者自定义非Windows定义的类型,如某种记录。 ­

­

3 、为便于调试,每个函数和过程应该尽可能短小精悍,并配合具体详细的注释。 ­

­

4 、应多利用try-finally来处理可能出现的错误和异常,注意这时要引用SysUtils单元。 ­

­

5 、尽可能少引用单元以减小DLL的大小,特别是不要引用可视化单元,如Dialogs单元。例如一般情况下,我们可以不引用Classes单元,这样可使编译后的DLL减小大约16Kb。 ­

­

二、调用技巧。 ­

1 、在用静态方法时,可以给被调用的函数或过程更名。在前面提到的C++编写的DLL例子中,如果去掉extern ”C”语句,C++会编译出一些奇怪的函数名,原来的TestC函数会被命名为@TestC$s等等可笑的怪名字,这是由于C++采用了C++ name mangling技术。这个函数名在Delphi中是非法的,我们可以这样解决这个问题: ­

改写引用函数为 ­

function TestC(i:integer):integer;stdcall; ­

external ’Cpp.dll’;name ’@TestC$s’; ­

其中name的作用就是重命名。 ­

­

2 、可把我们编写的DLL放到Windows目录下或者Windows/system目录下。这样做可以在external语句中或LoadLibrary语句中不写路径而只写DLL的名称。但这样做有些不妥,这两个目录下有大量重要的系统DLL,如果您编的DLL与它们重名的话其后果简直不堪设想,况且您的编程技术还不至于达到将自己编写的DLL放到系统目录中的地步吧! ­

­

三、调试技巧。 ­

1 、我们知道DLL在编写时是不能运行和单步调试的。有一个办法可以,那就是在Run|parameters菜单中设置一个宿主程序。在Local页的Host Application栏中添上宿主程序的名字就可进行单步调试、断点观察和运行了。 ­

­

2 、添加DLL的版本信息。开场白中提到了版本信息对于DLL是很重要的,如果包含了版本信息,DLL的大小会增加2Kb。增加这么一点空间是值得的。很不幸我们如果直接使用Project|options菜单中Version选项是不行的,这一点Delphi的帮助文件中没有提到,经笔者研究发现,只要加一行代码就可以了。如下例: ­

­

library Delphi; ­

­

uses ­

SysUtils, ­

Classes; ­

­

{$R *.RES} ­

//注意,上面这行代码必须加在这个位置 ­

­

function TestDll(i:integer):integer;stdcall; ­

begin ­

Result:=i; ­

end; ­

­

exports ­

TestDll; ­

­

begin ­

end. ­

­

3 、为了避免与别的DLL重名,在给自己编写的DLL起名字的时候最好采用字符数字和下划线混合的方式。如:jl_try16.dll。 ­

­

4 、如果您原来在Delphi 1或Delphi 2中已经编译了某些DLL的话,您原来编译的DLL是16位的。只要将源代码在新的Delphi 3或Delphi 4环境下重新编译,就可以得到32位的DLL了。 ­

­

[后记]:除了上面介绍的DLL最常用的使用方法外,DLL还可以用于做资源的载体。例如,在Windows中更改图标就是使用的DLL中的资源。另外,熟练掌握了DLL的设计技术,对使用更为高级的OLE、COM以及ActiveX编程都有很多益处。   2007-9-14 16:22:41    Delphi中如何调用DLL ­

马上想得到的使用说明有以下几点:­

­

1. 所需动态连结的 DLL 须置放在与执行档同一目录或Windows System 目录2. 确认 DLL export 出来的函式的原型, 以目前的情况而言, 通常只拿得到 C语言的函数原型,这时要注意 C 与 object Pascal 相对应的型别, 如果需要, 在interface 一节定义所需的资料类别­

­

3. 在 implementation 节中宣告欲使用的函式, 语法大致如下:­

­

procedure ProcName(Argu...); far; external ’DLL档名’;­

­

index n;­

­

function FuncName(Argr...): DataType; far;­

­

external ’DLL档名’; index n;­

­

宣告时, index n 如果不写, 便是参考资料中所谓 import by name 的方式, 此时, 由於需要从 DLL 的 name table 中找出这个函式, 因此, 连结执行速度比import by ordinal稍慢一些, 此外, 还有一种 by new name, 由於我没用过, 您可以查一参考资料, 大意是可以 import 後改用另一个程式命名呼叫这个函式­

­

4. 然後, 呼叫与使用就与一般的Delphi 没有两样5. 上述是直接写到呼叫DLL函式的程式单元中, 此外,也可以将DLL的呼叫宣告集中到一个程式单元(Import unit), Delphi 内附的 WinTypes, WinProcs是一个例子,­

­

您可以参考一下,同时观察一下 C 与 Pascal 互相对应的资料型态6. 除了上述的 static import 的方式, 另外有一种 dynamic import 的写法,先宣告一个程序类型(procedural-type),程式执行时, 以LoadLibrary() API Load进来後, 再以 GetProcAddress() API 取得函式的位址的方式来连结呼叫, 在ObjectPascal Language Guide P.132-133 有一个例子, 您可以参考看看­

­

如果要举个例子, 以下是从我以前的程式节录出来的片断:­

­

(* for CWindows 3.1 *)­

­

unit Ime31;­

­

interface­

­

uses­

­

SysUtils, WinTypes, WinProcs, Dialogs;­

­

type­

­

(* 必要的资料型态宣告 *)­

­

tDateNTime = record­

­

wYear, wMonth, wDay: word;­

­

wHour, wMin, wSec: word;­

­

end;­

­

TImePro = record­

­

hWndIme: HWnd; { IME handle }­

­

dtInstDate: tDateNTime; { Date and time of installation }­

­

wVersion: word; { the version of IME }­

­

szDescription: array[0..49] of byte; { Description of IME module}­

­

szName: array[0..79] of byte; { Module name of the IME }­

­

szOptions: array[0..29] of byte; { options of IME at startup}­

­

fEnable: boolean; { IME status; True=activated,False=deactivated }­

­

end;­

­

pTImePro = ^TImePro;­

­

function SetIme(const sImeFileName: string): boolean; far;­

­

implementation­

­

(* begin 呼叫 winnls.dll export 函数的宣告 *)­

­

function ImpSetIme(hWndIme: HWND; lpImePro: pTImePro): boolean;far; external ’winnls.dll’;­

­

(* end 呼叫 winnls.dll export 函数的宣告 *)­

­

(* -------------------------------------------------- *)­

­

(* SetIme(const sImeFileName: string): boolean;­

­

(* ======­

­

(* 切换到某一特定的输入法­

­

(*­

­

(* 传入引数:­

­

(* sImeFileName: 输入法 IME 档名, 例: phon.ime;­

­

(* 空字串: 英数输入法­

­

(*­

­

(* 传回值:­

­

(* True: 切换成功­

­

(* False: 失败­

­

(* -------------------------------------------------- *)­

­

function SetIme(const sImeFileName: string): boolean;­

­

var­

­

pImePro: pTImePro;­

­

begin­

­

Result := False;­

­

if MaxAvail < SizeOf(TImePro) then­

­

begin­

­

MessageDlg(’记忆体不足’, mtWarning, [mbOk], 0);­

­

Exit;­

­

end­

­

else­

­

begin­

­

New(pImePro);­

­

try­

­

if sImeFileName = ’’ then (* 空字串, 还原到英数输入法 *)­

­

pImePro^.szName[0] := 0­

­

else­

­

StrPCopy(@pImePro^.szName, sImeFileName);­

­

Result := ImpSetIme(0, pImePro); (* 呼叫 ImpSetIme *)­

­

finally­

­

Dispose(pImePro);­

­

end; { of try }­

­

end;­

­

end; { of SetIme }­

­

end.­

­

;  ­

­

DELPHI 中DLL开发常见问题的讨论:­

http://www.delphibbs.com/delphibbs/DispQ.asp?LID=3685176 ­

­

­

2007-4-6 19:25:51    如要返回字符串要用PChar,最好用PChar用out或var方式返回,PChar的内存分配和释放在调用函数处理:GetMem(p, Size);    FreeMem(p);­

­

而在被调用函数写的方式应该是:­

­

­

procedure GetStr(var Pstr: PChar);­

var­

  str: string­

begin­

  str := 'return string';­

  strCopy(PStr, PChar(str));­

end;­

­

调用函数写法:­

TGetStr= procedure(var Pstr: PChar);­

­

funtion GetDllStr: string­

var­

  DllHnd: THandle;­

  GetStr: TGetStr;­

  Str: PChar;­

  strPath: string;­

begin­

  AHaveWhere := 0;­

  DllHnd := LoadLibrary(PChar('testdll'));­

  try­

    if (DllHnd <> 0) then­

    begin­

      @GetStr :=GetProcAddress(DllHnd, 'GetStr');­

      if (@GetStr<>nil) then­

      begin­

        GetMem(Str, 1024);­

        try­

          GetStr(Filter);­

          result := StrPas(Filter);­

        finally­

          FreeMem(Str);­

        end;­

      end­

      else­

      begin­

        application.MessageBox(PChar('DLL加载出错,DLL可能不存在!'), PChar('错误'),­

          MB_ICONWARNING or MB_OK);­

      end;­

    end;­

  finally­

    FreeLibrary(DllHnd);­

  end;­

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马铃薯_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值