Dephi 之DLL技巧汇集参考文章:Delphi环境中编写调用DLL的方法和技巧

Dephi 之DLL技巧汇集参考文章:Delphi环境中编写调用DLL的方法和技巧http://www.chinaaspx.com/club/topic_11_3941.htm

在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了

参考文章:在Delphi与C++之间实现函数与对象共享  http://www.zahui.com/html/2/4202.htm

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 中动态链接库的使用 http://blog.csdn.net/feifei1018/admin/EditPosts.aspx

在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下程序组织的一种重要方式,使用动态链接库可以极大地保护用户在不同开发工具、不同时期所做的工作;利用动态链接库,用户可以逐步去构筑自己的程序模块库,为今后的工作积累素材。

你可能感兴趣的:(编程,c,C#,vc++,Delphi)