Dephi 之DLL技巧汇集

【导读】本文讲解关于Dephi 的DLL一些技巧
 

在Delphi中静态调用DLL top

调用一个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文件”等运行错误。

编写技巧

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了

=================================================================================

 

1.C++共享Delphi对象

要实现从C++调用 Delphi对象,首先要在Delphi单元的接口部分以及C++的头文件中说明需要共享的对象的接口,在对象接口中定义该对象包含哪些属性与方法,并说 明可供共享的部分。对象的共享,关键在于方法的共享。在Delphi语言中,要使一个对象可以被共享,可以把它说明为两个接口部分,暂称为"共享接口"与 "实现接口"。其中共享接口指明对象中哪些方法可被另一种语言所共享;实现接口则继承共享接口,并且在单元实现部分针对实现接口中的方法定义具体的实现。 要定义一个可供C++共享的Delphi对象,共享接口的说明应注意:

在Delphi程序里,要共享的方法必须被说明为抽象(abstract),而且虚拟(virtual );

在C++程序里,必须用关键字"virtual"及"=0"后缀,把从Delphi共享的方法说明成"pure virtual";

共享的对象方法必须在两种语言里都被说明成相同的调用方式,通常使用标准系统调用方式(stdcall)。

下面,举例说明这些规则,假设有这样的一个Delphi对象:

TTestObject=class

procedure Proc1(x:integer);

function Func1(x:integer):PChar;

procedure Proc2;

function Func2:integer;

end;

如果C++程序需要共享其中的方法Proc1、Func1,可把上述说明修改成以下形式:

STestObject=class

procedure Proc1(x:integer); virtual; abstract; stdcall;

function Func1(x:integer); virtual; abstract; stdcall;

end;

TTestObject=class(STestObject)

procedure Proc1(x:integer);

fuction Func1(x:integer):PChar;

procedure Proc2;

fuction Func2:integer;

end;

在C++程序中做如下对象原型说明:

class STestObject {

virtual void Proc1(int x)=0;

virtual char *Func1(int x)=0;

};

为了能在C++中成功地访问Delphi定义的类, Delphi接口说明时必须包含一个可共享的"制造函数(Factory Function)"CreateTestObject,该制造函数可被定义在动态链接库或目标文件(.OBJ)中,例如:

Library TestLib;

exports CreateTestObject;

function CreateTestObject:STestObject; stdcall;

begin

Result:=TTestObject.Create;

end;



end.

经过这样的处理,现在可在C++程序中使用这个由Delphi定义的对象,调用方式如下:

extern "C" STestObject stdcall *CreateTestObject();

void UseTestObject(void) {

STestObject *theTestObject=CreateTestObject();

theTestObject->Proc1(10);

Char *str=theTestObject->Func1(0);

}

当 调用制造函数CreateTestObject时,实际上已经在Delphi一侧占用了一个对象实例的空间,C++程序在针对该对象的所有处理完成后必须 考虑释放这一空间,具体的实现可在Delphi中定义一个类,如上述Proc1的共享方法Free,以此来完成这一任务:

STestObject=class

procedure Proc1(x:integer); virtual; abstract; stdcall;

function Func1(x:integer); virtual; abstract; stdcall;

procedure Free; virtual; abstract; stdcall;

end;



implementation



procedure TTestObject.Free;

begin



end;



end.

==============================================================================

 

2.Delphi共享C++对象

通常,程序员会考虑使用Delphi来编制用户界面,所以Delphi代码调用C++代码似乎显得更加实际些。其实,Delphi共享C++对象的实现方法与上述C++共享Delphi对象非常相似。用同样的共享接口与实现接口说明方法来定义C++的类:

class STestObjedt {

virtual void Proc1(int x)=0;

virtual char *Func1(int x)=0;

};

class TTestObjedt :public STestObject {

void Proc1(int x);

char *Func1(int x);

void Proc2();

int Func2();

void Free();

};

然后实现这些方法。同样地,C++对象需要一个与之对应的制造函数,这里以DLL为例

STestObject stdcall export *CreateTestObject() {

return (STestObject *) new TTestObject.Create;

}

Delphi代码可以通过调用制造函数CreateTestObject,很容易地在C++中创建实例,获得指向该实例的指针值,并以这个指针值来调用对象中的共享方法。当然,在进行完该对象的相关处理后,千万不要忘了调用Free释放占用的空间。

张维

摘 要:Delphi以其独特的面向控件的开发方式、强大的数据库功能以及快速的编译技术,使得它自发布起即格外引人注意。随着Delphi 5提供丰富的Internet应用,Delphi日益成为最重要的软件开发工具之一,它吸引了许多原Visual Basic、Foxpro、dBase甚至C++的程序员,而这些程序员使用Delphi时需要解决的一个重要问题就是怎样利用他们原有的代码。本文将介 绍Delphi与C++程序集成的方法,包括:

Delphi与C++之间函数的共享;

代码的静态链接和动态链接;

对象的共享。

函数的共享

在Delphi 中调用C++函数与C++调用Delphi函数相当直接,需要注意的是,Delphi 1默认的函数调用方式是Pascal方式,Delphi 4、Delphi 5的默认方式则是优化的cdecl调用方式,即register方式。要在C++与Delphi程序之间实现函数共享,除非有充分的原因,否则应该使用标 准系统调用方式,即stdcall方式。为了使C++编译器不将函数标记为"mang led",使Delphi编译器误认为函数是采用cdecl调用方式,应该在C++代码中,以extern "C "说明被共享的函数,如下例所示:

原型说明:

在C++中:

extern "C" int _stdcall TestFunc();

在Delphi中:

function TestFunc:integer; stdcall;

调用语法:

在C++中:

int i=TestFunc();

在Delphi中:

var i:integer;



begin



i:=TestFunc;



end;

共享函数的参数必须是两种语言都支持的变量类

==================================================================

 

在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本身大小写不敏感)。 

2、 动态调用或隐式装入 

使用WINDOWS API 函数 Loadlibrary 和GetprocAddress可以实现在运行时间里的动态装载DLL,并调用其中的过程。 

例如: 

  Type TMyProc=Procedure (Param:Pchar ) ;Stdcall;

  Var MyProc: TMyproc;

 MyHandle:THandle;

 MyHandle:=LoadLibrary (‘Mydll’) ;

 If MyHandle<  =0 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

二、 调用方式 

1、 通过过程、函数名; 

2、 通过过程、函数别名; 

3、 通过过程、函数的顺序号 

例: Function Getstring : string ; stdcall ; external ‘Mydlls.dll’ name ‘Mygetstr’name  子句指定函数名Getstring 改为Mygetstr,当程序调用这个例程时,使用Mygetstr这个名字; Function Getstring : string ; stdcall ; external ‘Mydlls.dll’ index 5 Index  子句通过索引号引入例程可以减少DLL的加载时间。 

三、 调用约定 

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

调用约定 参数传递顺序

Register 从左到右

Pascal 从左到右

Stdcall 从右到左

Cdecl 从右到左

Safecall 从右到左

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

四、 DLL中的变量和段 

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

 

参考文章:Delphi中如何调用VC++创建的动态链接库http://www.ccw.com.cn/htm/app/aprog/01_10_29_2.asp

Delphi 以其独特的面向控件的开发方式、强大的数据库功能、快速的编译技术以及简单易学的编程特性,使得它自发布之日起即格外引人注目,许多程序员也因此将它作为 首选的开发工具。然而,Delphi在科学计算、低端编程等方面的功能不如VC++。VC++功能强大、齐全,但是整个系统比较复杂、庞大,尤其对于初学 者来说比较难学,其用户界面的开发远不如Delphi那样方便、快捷。那么,我们能否将两者的优点结合起来呢?答案是肯定的!具体做法是:将涉及到比较低 级的操作、计算等方面的程序用VC++写成函数放在动态链接库中,而涉及到界面及与用户交互的编程则用Delphi来实现,最后只需在Delphi中调用 VC++编写的动态链接库即可。

 

=========================================================================

一、动态链接库简介 动态链接库(DLL,即 “Dynamic-Link Library”)是一个能够被应用程序和其它的DLL调用的过程和函数的集合体,它里面包含的是公共代码或资源。DLL是Windows的基石,所有的Win32 API函数都包含在DLL中。

使用DLL有许多优点:

1、一个DLL可以提供给不同的程序使用,如果有多个程序使用相同的DLL,也只需将DLL在内存中装载一次,这样就节省了内存开销。

2、DLL可以使我们的编程更加模块化,将功能相对独立的模块编成一个动态链接库,这样改动程序时不需将整个程序重新编译,只需重新编译所改动的模块。

3、使用了DLL组件包可以大大减小可执行文件的规模。

4、对于一个大型的、不断更新的应用程序,可以将许多重复的功能写成DLL,用主程序调用,这样既减少了开发的工作量,又提高了访问速度。

5、 DLL独立于编程语言,大多数WINDOWS编程环境都允许主程序调用DLL中的函数。即可以用VC++、VB、PowerBuilder、 Delphi、汇编语言等建立DLL,然后在不同语言编制的应用程序中调用它。这样就给多人使用不同的编程语言开发项目提供了极大的方便。

二、在Delphi中调用VC++创建的动态链接库的实例

(一)实验环境 本实例的编程工具及运行环境为:Windows 98,VC++6.0,Delphi 5.0 。

(二)实验内容

1.用VC++6.0建立一个动态链接库MaxMin.DLL,该库中包含有两个函数:返回三个整数中最大整数的函数Max1( )和返回三个整数中最小整数的函数Min1( )。

;2.用Delphi编写测试程序调用动态链接库MaxMin.DLL中的两个函数。

(三)实验步骤

1. 用VC++6.0建立动态链接库MaxMin.DLL 第一步:启动VC++6.0,选择“File/New/MFC AppWizzard(Dll)”,工程名设为“MaxMin”,按“确定”钮后,选择“Regular DLL Using shared MFC DLL”,按“Finish”钮后,即创造了一个DLL的框架工程。 第二步:选择“File/New”,在出现的对话框中选择“C/C++ Header File”,在文件名处输入“MyDLL”,按“确定”钮,即创建了一个空的头文件“MyDLL.h”。在该文件中输入以下两行内容: extern "C" _declspec(dllexport) int Min1(int x,int y,int z); extern "C" _declspec(dllexport) int Max1(int x,int y,int z); 选择“File/Save”保存该文件的内容。 第三步:选择“File/New”,在出现的对话框中选择“C/C++ Source File”, 在文件名处输入“MyDLL”,按“确定”钮,即创建了一个空的源文件“MyDLL.cpp”。在该文件中输入以下内容: #include "stdafx.h" #include "MyDll.h" extern "C" __declspec(dllexport) int Min1(int x,int y,int z) { if ((x<=y) & (x<=z)) return x; else if ((y<=x) & (y<=z)) return y; else return z; /*找出x,y,z中的最小整数*/ } extern "C" __declspec(dllexport) int Max1(int x,int y,int z) { if ((x>=y) & (x>=z)) return x; else if ((y>=x) & (y>=z)) return y; else return z; /*找出x,y,z中的最大整数*/ } 选择“File/Save”保存该文件的内容。 第四步:按下运行图标“!”,即生成了MyDLL.DLL(在当前工程目录的DEBUG子目录下)。 2.用Delphi编写调用MaxMin.DLL的测试程序 调用动态链接库有两种方法,即隐式调用和显式调用。 (1)隐式调用 第一步:启动Delphi,选择“New Application”,生成一个空的应用程序,在Form的“Name”属性处输入“TestVcDLLForm”,Caption属性处输入“VC ++的DLL隐式调用测试”,在Form中放入控件如表1所示(其中所有的Edit控件的“Text”属性均设为空):

(表1:所用到的控件及其属性) 

(图1:应用程序屏幕效果)

最 后设计的Form的屏幕效果如图1所示。选择“File/Save all”,在“Save unit1 as”对话框中将源文件名设为“main.pas”,按“保存”钮;在“Save Project1 as”对话框中将工程名设为“TestVcDLL”,按“保存”钮。

第二步:选择“File/New…”,在出现的“New Item”对话框中选择“unit”,按“OK”钮,生成一个空的源文件,在该文件中输入以下内容: unit MaxMin; interface function Min1(x,y,z:Integer):Integer; stdcall; function Max1(x,y,z:Integer):Integer; stdcall; implementation function Min1;external 'MaxMin.DLL' name 'Min1'; function Max1;external Max'Min.DLL' name 'Max1'; end. 选择“File/Save As…”,将上述文件存为“MaxMin.pas”。

 

=================================================

 

第三步:在Main.pas文件中,在“implementation”语句后加入: uses MaxMin;

 

第 四步:在Form上双击“运行”按钮对该按钮的“Click”事件编程,代码如下: procedure TTestVcDLLForm.btnRunClick(Sender: TObject); begin edtMax.Text:=IntToStr(Max1(StrToInt(edtInt1.Text), StrToInt(edtInt2.Text),StrToInt(edtInt3.Text))); //调用动态链接库中的函数Max1 edtMin.Text:=IntToStr(Min1(StrToInt(edtInt1.Text), StrToInt(edtInt2.Text),StrToInt(edtInt3.Text))); //调用动态链接库中的函数Min1 end; 保存该文件。

第五步:将上述1.中VC++6所建立的动态链接库“MaxMin.DLL”拷入Delphi的当前工作目录中。

第六步:运行。结果如图2所示。

图2:隐式调用DLL运行结果

图3:显式调用DLL运行结果

(2)显示调用 第一步 :同隐式调用。只是将Form的“Caption”属性改为“VC++的DLL显式调用测试”。

第二步 : 选择“File/New…”,在出现的“New Item”对话框中选择“unit”,按“OK”钮,生成一个空的源文件,在该文件中输入以下内容: unit Unit1; interface type TMin1=function(x,y,z:Integer):Integer; stdcall; TMax1=function(x,y,z:Integer):Integer; stdcall; THandle=Integer; implementation end. 选择“File/Save As…”,将上述文件存为“MaxMin.pas”。

第三步 :在Main.pas文件中,在“implementation”语句后加入: uses MaxMin;

第四步 : 在Form上双击“运行”按钮对该按钮的“Click”事件编程,代码如下: procedure TTestVcDLLForm.btnRunClick(Sender: TObject); var Handle:THandle; Min1:TMin1; Max1:TMax1; begin Handle:=LoadLibrary('MaxMin.dll'); //将“MaxMin.dll”的文件映象映射进调用进程的地址空间 if Handle<>0 then begin @Min1:=GetProcAddress(Handle,'Min1'); //取得DLL中函数Min1( )的地址 @Max1:=GetProcAddress(Handle,'Max1'); //取得DLL中函数Max1( )的地址 if (@Min1<>nil) and (@Min1<>nil) then begin edtMin.Text:=IntToStr(Min1(StrToInt(edtInt1.Text), StrToInt(edtInt2.Text),StrToInt(edtInt3.Text))); //调用动态链接库中的函数Min1 edtMax.Text:=IntToStr(Max1(StrToInt(edtInt1.Text), StrToInt(edtInt2.Text),StrToInt(edtInt3.Text))); //调用动态链接库中的函数Max1 end else ShowMessage('调用函数“GetProcAddress”时出错!'); FreeLibrary(Handle); //从进程的地址空间中解除“MaxMin.dll”文件的映射 end; end; 保存该文件。

第五步 :将上述1.中VC++6所建立的动态链接库“MaxMin.DLL”拷入Delphi的当前工作目录中。

第六步 :运行。结果如图3所示。 以上实例均编译通过,运行正确。

三、 结束语 动态链接库为不同编程环境下的应用程序之间的连接提供了方便,节省了内存,提高了速度,同时也丰富了PowerScript语言的编程能力。动态链接库是 Windows下程序组织的一种重要方式,使用动态链接库可以极大地保护用户在不同开发工具、不同时期所做的工作;利用动态链接库,用户可以逐步去构筑自 己的程序模块库,为今后的工作积累素材。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值