分类: 数据库开发技术
2008-05-28 15:53:30
在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++之间实现函数与对象共享
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为数据段)。