手把手教
delphi:
写你的
dll
文件
1、开使你的第一个DLL专案
1.File->Close all->File->New﹝DLL﹞
代码:
//
自动产生
Code
以下
library Project2;
//
这有段废话
uses
SysUtils,
Classes;
{$R *.RES}
begin
end.
2.加个Func进来:
代码:
library Project2;
uses
SysUtils,
Classes;
Function MyMax ( X , Y : integer ) : integer ; stdcall ;
begin
if X > Y then
Result := X
else
Result := Y ;
end ;
//
切记:
Library
的名字大小写不要紧,但是
DLL-Func
的大小写就有关系了。
//
在
DLL-Func-Name
写成
MyMax
与
myMAX
是不一样的。若是写错了,当即
//
的结果是你叫用到此
DLL
的
AP
根本开不起来。
//
参数的大小写就不要紧了。甚至没必要同名。如原型中是
(X,Y:integer)
但引
//
用时写成
(A,B:integer)
,那是不要紧的。
//
切记:要再加个
stdcall
。书上讲,若是你是用
Delphi
写
DLL
,且但愿不只给
// Delphi-AP
也但愿
BCB/VC-AP
等使用的话,那你最好加个
Stdcall ;
的指示
//
参数型态:
Delphi
有不少种它本身的变量型态,这些固然不是
DLL
所喜欢的
//
,
Windows/DLL
的母语应该是
C
。因此若是要传进传出
DLL
的参数,咱们
//
尽量照规矩来用。这二者写起来,后者会麻烦很多。若是你对
C
不熟
//
的话,那也不要紧。咱们之后再讲。
{$R *.RES}
begin
end.
3.将这些可共享的Func送出DLL,让外界﹝就是你的Delphi-AP啦﹞使用:光如此,你的AP还不能用到这些,你还要加个Exports才行。
代码:
{$R *.RES}
exports
MyMax ;
begin
end.
4.好了,能够按 Ctrl-F9编译了。此时可不要按F9。DLL不是EXE┌不可单独执行的,若是你按F9,会有ErrorMsg的。这时若是DLL有Error,请修正之。再按Ctrl-F9。此时可能有Warning,没关系,研究一下,看看就好。再按Ctrl-F9,此时就『Done , Compiled 』。同目录就会有个 *.dll 。恭喜,大功告成了。
2、进行测试:开个新application:
1.加个
TButton
代码:
ShowMessage ( IntToStr(MyMax(30,50)) ) ;
2.告知Exe到那里抓个
Func
代码:
//
在
Form,interface,var
后加
Function MyMax ( X , Y : integer ) : integer ; stdcall ; external 'MyTestDLL.dll' ;
// MyTestDLL.dll
为你前时写的
DLL
项目名字
// DLL
名字大小写不要紧。不过记得要加
extension
的
.DLL
。在
Win95
或
NT
,
//
是没必要加
extension
,但这两种
OS
,可能愈来愈少了吧。要加
extension
能够了,简单吧。
上面的例子是否是很简单?熟悉Delphi的朋友能够看出以上代码和通常的Delphi程序的编写基本是相同的,只是在TestDll函数后多了一个stdcall参数而且用exports语句声明了TestDll函数。只要编译上面的代码,就能够玫揭桓雒狣elphi.dll的动态连接库。如今,让咱们来看看有哪些须要注意的地方:
1.在DLL中编写的函数或过程都必须加上stdcall调用参数。在Delphi 1或Delphi 2环境下该调用参数是far。从Delphi 3之后将这个参数变为了stdcall,目的是为了使用标准的Win32参数传递技术来代替优化的register参数。忘记使用stdcall参数是常见的错误,这个错误不会影响DLL的编译和生成,但当调用这个DLL时会发生很严重的错误,致使操做系统的死锁。缘由是register参数是Delphi的默认参数。
2.所写的函数和过程应该用exports语句声明为外部函数。
正如你们看到的,TestDll函数被声明为一个外部函数。这样作可使该函数在外部就能看到,具体方法是单激鼠标右键用“快速查看(Quick View)”功能查看该DLL文件。(若是没有“快速查看”选项能够从Windows CD上安装。)TestDll函数会出如今Export Table栏中。另外一个很充分的理由是,若是不这样声明,咱们编写的函数将不能被调用,这是你们都不肯看到的。
3.当使用了长字符串类型的参数、变量时要引用ShareMem。
Delphi中的string类型很强大,咱们知道普通的字符串长度最大为256个字符,但Delphi中string类型在默认状况下长度能够达到2G。(对,您没有看错,确实是两兆。)这时,若是您坚持要使用string类型的参数、变量甚至是记录信息时,就要引用ShareMem单元,并且必须是第一个引用的。既在uses语句后是第一个引用的单元。以下例:
uses
ShareMem, SysUtils, Classes;
还有一点,在您的工程文件(*.dpr)中而不是单元文件(*.pas)中也要作一样的工做,这一点Delphi自带的帮助文件没有说清楚,形成了不少误会。不这样作的话,您颇有可能付出死机的代价。避免使用string类型的方法是将string类型的参数、变量等声明为Pchar或ShortString(如:s:string[10])类型。一样的问题会出如今当您使用了动态数组时,解决的方法同上所述。
用
Delphi
制做
DLL
的方法
一 Dll的制做通常步骤
二 参数传递
三 DLL的初始化和退出清理[若是须要初始化和退出清理
]
四 全局变量的使用
五 调用静态载入
六 调用动态载入
七 在DLL创建一个
TForM
八 在DLL中创建一个
TMDIChildForM
九 示例:
十 Delphi制做的Dll与其余语言的混合编程中常遇问题:
十一 相关资料
一 Dll的制做通常分为如下几步:
1 .在一个DLL工程里写一个过程或函数
2 .写一个Exports关键字,在其下写过程的名称。不用写参数和调用后缀。
二 参数传递
1 .参数类型最好与window C++的参数类型一致。不要用DELPHI的数据类型。
2 .最好有返回值[即便是一个过程],来报出调用成功或失败,或状态。成功或失败的返回值最好为1[成功]或0[失败].一句话,与windows c++兼容。
3 .用stdcall声明后缀。
4 .最好大小写敏感。
5 .无须用far调用后缀,那只是为了与windows 16位程序兼容。
三 DLL的初始化和退出清理[若是须要初始化和退出清理
]
1 .DLLProc[SysUtils单元的一个Pointer]是DLL的入口。在此你可用你的函数替换了它的入口。但你的函数必须符合如下要求[其实就是一个回调函数]。以下:
procedure DllEnterPoint(dwReason: DWORD);far;stdcall;
|
dwReason参数有四种类型:
DLL_PROCESS_ATTACH:进程进入时
DLL_PROCESS_DETACH进程退出时
DLL_THREAD_ATTACH 线程进入时
DLL_THREAD_DETACH 线程退出时
在初始化部分写:
DLLProc := @DLLEnterPoint;
DllEnterPoint(DLL_PROCESS_ATTACH);
|
2 .如Form上有TdcomConnection组件,就Uses Activex,在初始化时写一句
CoInitialize (nil);
3 .在退出时必定保证DcomConnection.Connected := False,而且数据集已关闭。不然报地址错。
四 全局变量的使用
在widnows 32位程序中,两个应用程序的地址空间是相互没有联系的。虽然DLL在内存中是一份,但变量是在各进程的地址空间中,所以你不能借助dll的全局变量来达到两个应用程序间的数据传递,除非你用内存映像文件。
五 调用静态载入
1 客户端函数声名:
1)大小写敏感。
2)与DLL中的声明同样。
如: showform(form:Tform);Far;external'yproject_dll.dll';
3)调用时传过去的参数类型最好也与windows c++同样。
4)调用时DLL必须在windows搜索路径中,顺序是:当前目录;Path路径;
windows;widows\system;windows\ssystem32;
六 调用动态载入
1 .创建一种过程类型[若是你对过程类型的变量只是一个指针的本质清楚的话,你就知道是怎么回事了]。如:
type
mypointer=procedure(form:Tform);Far;external;
var
Hinst:Thandle;
showform:mypointer;
begin
Hinst:=loadlibrary('yproject_dll');//Load
一个
Dll,
按文件名找。
showform:=getprocaddress(Hinst,'showform');//
按函数名找,大小写敏感。若是你知道自动化对象的本质就清楚了。
showform(application.mainform);//
找到函数入口指针就调用。
Freelibrary(Hinst);
end;
|
七 .在DLL创建一个
TForM
1 把你的Form Uses到Dll中,你的Form用到的关联的单元也要Uses进来[这是最麻烦的一点,由于你的Form或许Uses了许多特殊的单元或函数
]
2 传递一个Application参数,用它创建Form.
八 .在DLL中创建一个
TMDIChildForM
1 Dll中的MDIForm.FormStyle不用为
fmMDIChild.
2 在CreateForm后写如下两句:
function ShowForm(mainForm:TForm):integer;stdcall
var
Form1: TForm1;
ptr:PLongInt;
begin
ptr:=@(Application.MainForm);//
先把
dll
的
MainForm
句柄保存起来,也无须释放,只不过是替换一下
ptr^:=LongInt(mainForm);//
用主调程序的
mainForm
替换
DLL
的
MainForm
。
MainForm
是特殊的
WINDOW
,它专门管理
Application
中的
Forms
资源
.
//
为何不直接
Application.MainForm := mainForm,
由于
Application.MainForm
是只读属性
Form1:=TForm1.Create(mainForm);//
用参数创建
end;
|
备注:参数是主调程序的Application.MainForm
九 .示例:
DLL源代码:
library Project2;
uses
SysUtils, Classes,
Dialogs,
Forms,
Unit2 in 'Unit2.pas' {Form2};
{$R *.RES}
var
ccc: Pchar;
procedure OpenForm(mainForm:TForm);stdcall;
var
Form1: TForm1;
ptr:PLongInt;
begin
ptr:=@(Application.MainForm);
ptr^:=LongInt(mainForm);
Form1:=TForm1.Create(mainForm);
end;
procedure InputCCC(Text: Pchar);stdcall;
begin
ccc := Text;
end;
procedure ShowCCC;stdcall;
begin
ShowMessage(String(ccc));
end;
exports
OpenForm;
InputCCC,
ShowCCC;
begin
end.
|
调用方源代码:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
|
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure OpenForm(mainForm:TForm);stdcall;External'project2.dll';
procedure ShowCCC;stdcall;External'project2.dll';
procedure InputCCC(Text: Pchar);stdcall;External'project2.dll';
procedure TForm1.Button1Click(Sender: TObject);
var
Text: Pchar;
begin
Text := Pchar(Edit1.Text);
// OpenForm(Application.MainForm);//
为了调
MDICHILD
InputCCC(Text);//
为了实验
DLL
中的全局变量是否在各个应用程序间共享
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
ShowCCC;//
这里代表
WINDOWS 32
位应用程序
DLL
中的全局变量也是在应用程序地址空间中,
16
位应用程序或许不一样,没有作实验。
end;
|
十 Delphi制做的Dll与其余语言的混合编程中常遇问题:
1 .与PowerBuilder混合编程
在定义不定长动态数组方面在函数退出清理堆栈时老出现不可重现的地址错,缘由未明,大概与PB的编译器原理有关,即便PB编译成二进制代码也如此。
在
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中定义的其余函数同样简单。注意事项有如下一些:
1、调用参数用stdcall
和前面提到的同样,当引用DLL中的函数和过程时也要使用stdcall参数,缘由和前面提到的同样。
2、用external语句指定被调用的DLL文件的路径和名称
正如你们看到的,咱们在external语句中指定了所要调用的DLL文件的名称。没有写路径是由于该DLL文件和调用它的主程序在同一目录下。若是该DLL文件在C:\,则咱们可将上面的引用语句写为external ’C:\Delphi.dll’。注意文件的后缀.dll必须写上。
3、不能从DLL中调用全局变量
若是咱们在DLL中声明了某种全局变量,如:var s:byte 。这样在DLL中s这个全局变量是能够正常使用的,但s不能被调用程序使用,既s不能做为全局变量传递给调用程序。不过在调用程序中声明的变量能够做为参数传递给DLL。
4、被调用的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。
1、定义所要调用的函数或过程的类型
在上面的代码中咱们定义了一个TIntFunc类型,这是对应咱们将要调用的函数TestC的。在其余调用状况下也要作一样的定义工做。而且也要加上stdcall调用参数。
2、释放所调用的DLL
咱们用LoadLibrary动态的调用了一个DLL,但要记住必须在使用完后手动地用FreeLibrary将该DLL释放掉,不然该DLL将一直占用内存直到您退出Windows或关机为止。
如今咱们来评价一下两种调用DLL的方法的优缺点。静态方法实现简单,易于掌握而且通常来讲稍微快一点,也更加安全可靠一些;可是静态方法不能灵活地在运行时装卸所需的DLL,而是在主程序开始运行时就装载指定的DLL直到程序结束时才释放该DLL,另外只有基于编译器和连接器的系统(如Delphi)才可使用该方法。动态方法较好地解决了静态方法中存在的不足,能够方便地访问DLL中的函数和过程,甚至一些老版本DLL中新添加的函数或过程;但动态方法难以彻底掌握,使用时由于不一样的函数或过程要定义不少很复杂的类型和调用方法。对于初学者,笔者建议您使用静态方法,待熟练后再使用动态调用方法。
使用
DLL
的实用技巧
1、编写技巧
1 、为了保证DLL的正确性,可先编写成普通的应用程序的一部分,调试无误后再从主程序中分离出来,编译成DLL。
2 、为了保证DLL的通用性,应该在本身编写的DLL中杜绝出现可视化控件的名称,如:Edit1.Text中的Edit1名称;或者自定义非Windows定义的类型,如某种记录。
3 、为便于调试,每一个函数和过程应该尽量短小精悍,并配合具体详细的注释。
4 、应多利用try-finally来处理可能出现的错误和异常,注意这时要引用SysUtils单元。
5 、尽量少引用单元以减少DLL的大小,特别是不要引用可视化单元,如Dialogs单元。例如通常状况下,咱们能够不引用Classes单元,这样可以使编译后的DLL减少大约16Kb。
2、调用技巧
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放到系统目录中的地步吧!
3、调试技巧
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
编程都有不少益处。
对使用
Delphi
制做
DLL
复用文件的建议
在公司里有一些须要制做
DLL
的场合,由于熟悉、方便和简易,大多数使用
Delphi
来制做。如今就这个主题提出一些我的建议。
尽可能使用标准
DLL
接口。指的是传递的参数类型及函数返回类型不能是
Delphi
特有的,好比
string
(
AnsiString
),以及动态数组和含有这些类型成员的复合类型(如记录),也不能是包含有这些类型成员数据成员的对象类型,以免可能的错误。若是使用了
string
类型或动态数组类型,且调用方不是
Delphi
程序,则基本上会报错。若是调用方是
Delphi
但调用方或被调用方没有在工程文件的第一包含单元不是
ShareMem
,也可能会出错。
若是调用方是
Delphi
应用程序,则可能可使用不包含禁止类型(
string,
动态数组)数据成员的对象做为参数或返回值,但也应尽可能避免。
若是调用方与被调用方都是
Delphi
程序,并且要使用
string
或动态数组做参数,则双方工程文件的第一包含单元必须是
ShareMem
。(
C++Builder
程序的状况可能与此相同,不过没有测试过。)
若是调用方不是
Delphi
程序,则
string
、动态数组、包含
string
或动态数组的复合数据类型及类实例,都不能做为参数及返回值。
所以,为了提升
DLL
的复用范围,避免可能存在的错误,应当使用标准
WIN32 API
标准参数类型,之前使用
string
的变量,可使用
PChar(s)
转换。动态数组则转换为指针类型(
@array[0]
),并加上数组的长度。
若是由于调用方与被调用方都是
Delphi
程序,为了编写方便,不想进行上述转换,则推荐使用运行时包的形式。运行时包能够保证动态分配数据的正确释放。这样由于其扩展名(
.bpl
),显出该文件仅限于
Delphi/C++Builder
使用(不象
DLL
)。
其次,尽可能避免使用
overload
的函数
/
过程做输出,若是同一操做有多个方式,则可让函数
/
过程名有少量差异,相似于
Delphi
中的
FormatXXXX
、
CreateXXXX
等函数及方法,如
CreateByDefaultFile, CreateDefault
。
最后,做为
DLL
的提供者,应当提供直接编程的接口文件,如
Delphi
中的
.pas
或
.dcu
(最好是
.pas
,由于能够有注释)、
C
及
C++
中的
.h
和
.lib
。而不是让使用者们本身建立。若是非要有
overload
的函数
/
过程,这一点显得特别重要。另外,做为
Delphi
应用,提供的
.pas
文件能够是提早链接的(使用
external
指定
DLL
中的输出函数),也能够是后期链接的(使用
LoadLibrary
、
GetProcAddress
),
DLL
提供者提供编程接口文件,既显得正式(或
HiQoS
),又有保障。