dll使用经验总结

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.

你可能感兴趣的:(Delphi)