dll使用经验总结
【资料是从免费网站上获取的,只为交流学习目的,文章原作者保留所有权力】
1.共享代码、资源和数据
2.隐藏实现的细节
3.自定义控件
4.当多个程序同时调用同一个DLL时,能节约系统资源。
5.实现程序的模块化,使程序容易维护。
注意:
1.多个程序调用同一个DLL时,并不能共享该DLL中的数据,但可以通过内存映射文件技术来实现。
设置DLL的首选基地址:
2.如果在DLL被调入进程的地址空间时设置了基地址,则DLL数据就可以被共享。是通过使用$IMAGEBASE
指示符来给每个DLL设置一个基地址的。一般可执行文件(EXE和DLL)的缺省基地址为$400000,这样,除非
修改DLL的基地址,否则就会与主程序的基地址引起冲突,因此进程间也就不能共享DLL的数据。如果DLL
的基地址与已经分配的DLL地址重叠的话,Win32系统会重新分配基地址。
3.如果DLL中的导出函数或过程以字符串或动态数组作为参数或返回值,则ShareMem必须是DLL和项目中uses子句的第一单元。
ShareMem是共享的内存管理器Borlndmm.dll的接口单元,当在DLL中加入该DLL文件后,必须将它一起发布。为了避免使用Borlndmm.dll,
就得用PChar或ShorString来传递字符串信息。
----------
二创建DLL和调用DLL:
(一).创建一个DLL项目并生成DLL文件:
1.在New Items对话框中的New标签中选择DLL Wizard,生成DLL项目。
2.在DLL项目的工程文件中加入功能代码:例如:
//--------DLL项目的工程文件----------
library MaxValue; //DLL项目名,也是DLL文件名
{$DEFINE MaxValue} //编译指令,为了在编译DLL时不重复编译导出单元
uses
SysUtils,
Classes,
{下面一句是该DLL项目的单元文件,作为该DLL文件的接口单元,可通过File/New/Unit来为DLL项目加入接口单元。
注意:当向程序员提供该DLL文件时,要同时提供所有的接口单元文件,并且若该DLL要使用在其它语言上,则应先
将所有的接口单元重新编译成对应的语言形式。当向程序的用户提供该DLL文件时则不用。}
Max in 'Max.pas';
{$R *.res}
{以下是该DLL项目的功能代码部分}
function GetMaxValue(v1,v2:Integer):Integer;stdcall; //stdcall是调用协议
begin
if(v1>=v2)then Result:=v1
else Result:=v2;
end;
exports //exports部分是该DLL文件向外提供的函数或过程的接口,导出多个例程时要用逗号隔开。
GetMaxValue;
begin
//有内容要加入
end.
3.为DLL文件加入接口单元:接口单元可以是有窗体的,也可以是没有窗体的。
(1).没有窗体的接口单元
//-------该DLL项目的接口单元文件----------
unit Max;
interface
uses ...; //所需的单元文件,若没有则不用加入uses部分
type
...; //用户自定义类型部分,若没有则不用加入type部分,这部分的类型可以提供给调用DLL的程序使用。
{$IFNDEF MaxValue}
function GetMaxValue(v1,v2:Integer):Integer;stdcall; //DLL文件导出函数或过程的声明
{$ENDIF}
implementation
{$IFNDEF MaxValue}
function GetMaxValue;external 'MaxValue.dll' name 'GetMaxValue';
{DLL文件导出函数或过程的索引,这是按函数名进行索引的,还有另一种是按DLL文件中exports部分中导出函
数的排列顺序来进行索引。例如:
function GetMaxValue;external 'MaxValue.dll' index 1;
这两种索引方式的区别是:使用按函数名索引的方式,当DLL文件改动时,只要导出函数的名称不变,就不会引起调用错误;使用按序号
索引的方式,当DLL文件改动时,要确保导出函数的顺序不变,才不会引起调用错误。建议使用按函数名索引的方式。}
{$ENDIF}
end.
(2).具有窗体的DLL接口单元:
unit DLLUnit1; //DLL的接口单地元
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
...
private
{ Private declarations }
public
{ Public declarations }
end;
procedure ShowDLLForm(AHandle:THandle;ACaption:String);stdcall;//声明要导出的函数
implementation
{$R *.dfm}
procedure ShowDLLForm(AHandle:THandle;ACaption:String);stdcall;
var DLLForm:TForm1;
begin
Application.Handle:=AHandle; //将调用该DLL的进程的实例句柄赋给Application对象,否则对DLL中的窗体的操作将有问题
DLLForm:=TForm1.Create(Application);
try
DLLForm.Caption:=ACaption;
DLLForm.ShowModal;//显示DLL中的模式窗体,也可以是非模式窗体,即DLLForm.Show;
finally
DLLForm.Free;
end;
end;
end.
(二).在程序中调用DLL文件:
调用DLL文件有两种方式:隐式调用和显式调用。隐式调用也有两种方式:使用DLL的接口单元和不使用DLL的接口单元(当没有DLL相的相应
接口单元时)。
1。隐式调用:
(1).使用DLL的接口单元:在程序的编译搜索路径上提供所有用到的DLL的接口单元文件,并且将DLL文件放在程序的工程目录中。
//-------调用该DLL文件的其它程序的单元文件----------
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses Max; //在此包含进所要调用的DLL文件的接口单元即可。
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(IntToStr(GetMaxValue(StrToInt(Edit1.Text),StrToInt(Edit2.Text))));//GetMaxValue()函数为DLL文件提供的函数。
end;
end.
(2).不使用DLL的接口单元:将DLL文件放在程序的工程目录中。
//-------调用该DLL文件的其它程序的单元文件----------
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
function GetMaxValue(v1,v2:Integer):Integer;stdcall; //声明DLL中导出的例程。
var
Form1: TForm1;
implementation
{$R *.dfm}
function GetMaxValue;external 'MaxValue.dll';//指定函数的定义在DLL文件中。
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(IntToStr(GetMaxValue(StrToInt(Edit1.Text),StrToInt(Edit2.Text))));//GetMaxValue()函数为DLL文件提供的函数。
end;
end.
注意:隐式调用的这两种方式的区别是,使用接口单元时,可以使用接口单元中的自定义类型;而不使用接口单元时,则需要重新定义接口
单元中所有的自定义类型,以便能正确调用DLL导出的函数。当只需调用DLL中的少数例程时,采用不使用接口单元的方式,当需要调用DLL中
的大量例程或需要使用接口单元中的自定义类型时,采用使用接口单元的方式。
2.显式调用:
采用隐式调用时,程序一运行就将DLL调入内存,若程序只调用DLL中的少数例程,而DLL中又有大量的例程,则这种方式会浪费内存。这时,
采用显式调用才是最好的方法。其次,采用隐式调用方式的程序,若找不到DLL文件,则整个程序都不能运行,而采用显式调用方式的程序就
不会有这种问题。
//-------调用该DLL文件的其它程序的单元文件----------
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
{先为将要调用的DLL中的例程定义相应的函数指针类型或过程指针类型}
TGetMaxValue=function(v1,v2:Integer);Integer;stdcall; //函数指针类型
TShowDLLForm=procedure(AHandle:THandle;ACaption:String);stdcall; //过程指针类型
EDLLLoadError=class(Exception); //再定义一个异常类,处理加载DLL失败时的情况。
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
DLLHandle:THandle;
MaxFun:TGetMaxValue;
begin
DLLHandle:=LoadLibrary('MaxValue.dll'); //加载DLL文件
try
if DLLHandle=0 then
Raise EDLLLoadError.Create('Unable To Load DLL');
@MaxFun:=GetProcAddress(DLLHandle,'GetMaxValue'); //在DLL中获取相应例程的内存地址。
if @MaxFun<>nil then
ShowMessage(IntToStr(MaxFun(StrToInt(Edit1.Text),StrToInt(Edit2.Text))))//调用DLL中的函数。
else RaiseLastWin32Error; //触发异常。
finally
FreeLibrary(DLLHandle); //释放DLL
end;
end;
end.
----------
三.DLL的入口/出口函数:当进程调用或释放DLL时或当加载了DLL的进程创建新线程或删除线程时,就会触发DLL的入口/出口函数的调用。
这样就可以利用该入口/出口函数对进程、线程进行初始化或清空等操作。以下是个例子:
//----------DLL的入口/出口函数----------
library TextDLL;
uses
SysUtils,
Classes,
Windows,
Dialogs;
{$R *.res}
procedure DLLEntryPoint(dwReason:DWord); //这就是DLL的入口/出口函数
begin
case dwReason of
DLL_Process_Attach:ShowMessage('Attaching to process'); //这个事件是在DLL映射到进程的地址空间中时触发的
DLL_Process_Detach:ShowMessage('Detaching from process'); //这个事件是在DLL从进程的地址空间中释放出来时触发的
DLL_Thread_Attach:ShowMessage('Attaching to thread'); //这个事件是在加载了DLL后并创建新线程时触发的
DLL_Thread_Detach:ShowMessage('Detaching from trhead'); //这个事件是在加载了DLL后并消毁线程时触发的。
end;
end;
begin
{以下是个过程指针,把入口/出口函数的地址赋给它的原因是,当加载了该DLL的进程创建或消毁线程时或释放该DLL时,就会自动通过
DLLProc过程指针调用入口/出口函数,并传递不同的参数。}
DLLProc:=@DLLEntryPoint;
DLLEntryPoint(DLL_Process_Attach);//这一句是使调用该DLL的进程在加载该DLL时调用入口/出口函数。
end.
//----------调用DLL的程序的单元文件----------
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TTextThread=class(TThread) //自定义线程类
procedure Execute;override;
procedure SetCaptionData;
end;
var
Form1: TForm1;
DLLHandle:THandle;
TextThread:TTextThread;
ThreadCounter:Integer;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
DLLHandle:=LoadLibrary('TextDLL.dll');
if DLLHandle=0 then Beep
else ShowMessage('DLL has been Loaded');
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
if DLLHandle<>0 then
begin
FreeLibrary(DLLHandle);
DLLHandle:=0;
end;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
if TextThread=nil then
begin
TextThread:=TTextThread.Create(false);
end;
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
if TextThread<>nil then
begin
TextThread.Free;
TextThread:=nil;
end;
end;
{ TTextThread }
procedure TTextThread.Execute;
begin
inherited;
while TextThread<>nil do
begin
Synchronize(SetCaptionData);
Inc(ThreadCounter);
end;
end;
procedure TTextThread.SetCaptionData;
begin
Form1.Caption:=IntToStr(ThreadCounter);
end;
end.
----------
四.在不同进程间共享DLL中的数据:
在win32环境下,DLL被映射到每一个调用DLL的进程的地址空间中,相当于每个进程都拥有DLL的一份独立的实例(实际上内存
中只有DLL的一份实例),所以进程间不能共享DLL中的数据,也就是说,某个进程修改了DLL中的数据,并不会影响到调用该DLL
的其它进程。但如果要共享DLL中的数据,则必须通过内存映射文件技术来实现。例如,以下是一个具有在进程间共享数据的DLL:
library ShareDataDLL;
uses
ShareMem,
SysUtils,
Classes,
Windows;
{$R *.res}
{$I DLLDATA.INC} //编译指令,将包含文件DLLDATA.INC包含到DLL文件中。
const FileMappingName='SharedMapData'; //内存映射文件对象的名称
var
ShareData:PGlobalDLLData; //这就是用来在进程间共享数据的全局变量,其类型可以是其它的类型。它指向内存中的一块区域。
{PGlobalDLLData类型在DLLDATA.INC包含文件中声明,它是一个记录的指针类型,如下所示:
type
PGlobalDLLData=^TGlobalDLLData;
TGlobalDLLData=record
S:String[50];
I:Integer;
end;
}
HMap:THandle; //内存映射对象的句柄
procedure GetDLLData(var AShareData:PGlobalDLLData);stdcall;
begin
AShareData:=ShareData; //让调用该DLL文件的进程传来的指针变量指向同一块内存区域,就可实现数据共享
end;
procedure OpenShareData;
var Size:Integer;
begin
Size:=SizeOf(PGlobalDLLData);
HMap:=CreateFileMapping(DWORD(-1),nil,PAGE_READWRITE,0,Size,FileMappingName);//创建内存映对象
if HMap=0 then RaiseLastWin32Error;
ShareData:=MapViewOfFile(HMap,FILE_MAP_ALL_ACCESS,0,0,Size); //将ShareData指向创建的内存映射对象
ShareData^.S:='ShareLib';
ShareData^.I:=1;
if ShareData=nil then
begin
CloseHandle(HMap);
RaiseLastWin32Error;
end;
end;
procedure CloseShareData;
begin
UnMapViewOfFile(ShareData);
CloseHandle(HMap);
end;
procedure DLLEntryPoint(dwReason:DWord); //这就是DLL的入口/出口函数
begin
case dwReason of
DLL_Process_Attach:OpenShareData; //这个事件是在DLL映射到进程的地址空间中是触发的
DLL_Process_Detach:CloseShareData; //这个事件是在DLL从进程的地址空间中释放出来时触发的
end;
end;
exports
GetDLLData; //导出过程,调用该DLL的进程只要调用该过程,并传递一个适当类型的指针作为参数,即可实现数据共享了。
begin
{以下是个过程指针,把入口/出口函数的地址赋给它的原因是,当加载了该DLL的进程创建或消毁线程时或释放该DLL时,就会自动通过
DLLProc过程指针调用入口/出口函数,并传递不同的参数。}
DLLProc:=@DLLEntryPoint;
DLLEntryPoint(DLL_Process_Attach);//这一句是使调用该DLL的进程在加载该DLL时调用入口/出口函数。
end.
----------
五.引出DLL中的对象:
需要注意以下规则:
1.进程只能访问DLL中对象的虚拟方法。
2.DLL中的对象实例只能在DLL中创建。
3.进程和DLL必须都有对象及其方法的定义,其方法的顺序必须一致。
4.DLL中的对象不能被继承。
----------
六、创建导出函数的参数是回调函数的DLL
回调函数不是被应用程序本身调用,而是被Win32 DLL或其他DLL调用。这样就可以根据不同应用需要而传递不同的回调用函数给DLL,从而
实现不同的功能。创建这样的DLL,如下所示:
library StrSrchLib;
uses
WinProcs, //这是TFarProc类型所必需的
SysUtils;
type
{ declare the callback function type }
TFoundStrProc = procedure(StrPos: PChar); StdCall;
function SearchStr(ASrcStr, ASearchStr: PChar; AProc: TFarProc): Integer; StdCall;
{ 这个导出函数将判断是否有传递一个回调函数进来,如果有则调用这个回调函数,否则进行默认处理 }
var
FindStr: PChar;
begin
FindStr := ASrcStr;
FindStr := StrPos(FindStr, ASearchStr);
while FindStr <> nil do
begin
if AProc <> nil then
TFoundStrProc(AProc)(FindStr);
FindStr := FindStr + 1;
FindStr := StrPos(FindStr, ASearchStr);
end;
end;
exports
SearchStr;
begin
end.