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 文件 ” 等运行错误。
编写技巧
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;