引入文件

DLL比较复杂时,可以为它的声明专门创建一个引入单元,这会使该DLL变得更加容易维护和查看。引入单元的格式如下:
  unit MyDllImport; {Import unit for MyDll.dll }
  interface
  procedure MyDllProc;

implementation
   procedure MyDllProc;external 'MyDll' index 1;

end.

这样以后想要使用MyDll中的例程时,只要简单的在程序模块中的uses子句中加上MyDllImport即可。其实这仅仅是种方便开发的技巧,大家打开Windows等引入windows API的单元,可以看到类似的做法。

动态(显式)调用DLL

前面讲述静态调用DLL时提到,DLL会在启动调用程序时即被调入。所以这样的做法只能起到公用DLL以及减小运行文件大小的作用,而且DLL装载出错会立刻导致整个启动过程终止,哪怕该DLL在运行中只起到微不足道的作用。

使用动态调用DLL的方式,仅在调用外部例程时才将DLL装载内存(引用记数为0时自动将该DLL从内存中清除),从而节约了内存空间。而且可以判断装载是否正确以避免调用程序崩溃的情况,最多损失该例程功能而已。

动态调用虽然有上述优点,但是对于频繁使用的例程,因DLL的调入和释放会有额外的性能损耗,所以这样的例程则适合使用静态引入。

调用范例

DLL动态调用的原理是首先声明一个函数/过程类型并创建一个指针变量。为了保证该指针与外部例程指针一致以确保赋值正确,函数/过程的声明必须和外部例程的原始声明兼容(兼容的意思是1、参数名称可以不一样;2、参数/返回值类型至少保持可以相互赋值,比如原始类型声明为Word,新的声明可以为Integer,假如传递的实参总是在Word的范围内,就不会出错)。

接下来通过windows API函数LoadLibrary引入指定的库文件,LoadLibrary的参数是DLL文件名,返回一个THandle。如果该步骤成功,再通过另一个API函数GetProcAddress获得例程的入口地址,参数分别为LoadLibrary的指针和例程名,最终返回例程的入口指针。将该指针赋值给我们预先定义好的函数/过程指针,然后就可以使用这个函数/过程了。记住最后还要使用API函数FreeLibrary来减少DLL引用记数,以保证DLL使用结束后可以清除出内存。这三个API函数的Delphi声明如下:

Function LoadLibrary(LibFileName:PChar):THandle;

Function GetProcAddress(Module:THandle;ProcName:PChar):TfarProc;

Procedure FreeLibrary(LibModule:THandle);

将前面静态调用DLL例程的代码更改为动态调用,如下所示:

type

TDllProc = function (PathName : Pchar):boolean;stdcall;

var

LibHandle: THandle;

DelPath : TDllProc;

begin

LibHandle := LoadLibrary(PChar('FileOperate.dll'));

if LibHandle >= 32 then begin

try

DelPath := GetProcAddress(LibHandle,PChar('DeleteDir'));

if DirectoryExists(ShellTreeView.Path) then

if Application.MessageBox(Pchar('确定删除目录'+QuotedStr(ShellTreeView.Path)+'吗?'), 'Information',MB_YESNO) = IDYes then

if DelPath(PChar(ShellTreeView.Path)) then

showmessage('删除成功');

finally

FreeLibrary(LibHandle);

end;

end;

end;

16位DLL的动态调入

下面将演示一个16位DLL例程调用的例子,该例程是windows9x中的一个隐藏API函数。代码混合了静态、动态调用两种方式,除了进一步熟悉外,还可以看到调用16位DLL的解决方法。先解释一下问题所在:

我要实现的功能是获得win9x的“系统资源”。在winNT/2000下是没有“系统资源”这个概念的,因为winNT/2000中堆栈和句柄不再象win9X那样被限制在64K大小。为了取该值,可以使用win9x的user dll中一个隐藏的API函数GetFreeSystemResources。

该DLL例程必须动态引入。如果静态声明的话,在win2000里执行就会立即出错。这个兼容性不解决是不行的。所以必须先判断系统版本,如果是win9x再动态加载。检查操作系统版本的代码是:

var

OSversion : _OSVERSIONINFOA;

FWinVerIs9x: Boolean;

begin

OSversion.dwOSVersionInfoSize := sizeof(_OSVERSIONINFOA);
GetVersionEx(OSversion);
FWinVerIs9x := OSversion.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS;

End;

以上直接调用API函数,已在Windows单元中被声明。

function LoadLibrary16(LibraryName: PChar): THandle; stdcall; external kernel32 index 35;

procedure FreeLibrary16(HInstance: THandle); stdcall; external kernel32 index 36;

function GetProcAddress16(Hinstance: THandle; ProcName: PChar): Pointer; stdcall; external kernel32 index 37;

function TWinResMonitor.GetFreeSystemResources(SysResource: Word): Word;

type

TGetFreeSysRes = function (value : integer):integer;stdcall;

TQtThunk = procedure();cdecl;

var

ProcHandle : THandle;

GetFreeSysRes : TGetFreeSysRes;

ProcThunkH : THandle;

QtThunk : TQtThunk;

ThunkTrash: array[0..$20] of Word;

begin
Result := 0;
ThunkTrash[0] := ProcHandle;
if FWinVerIs9x then begin
ProcHandle := LoadLibrary16('user.exe');
if ProcHandle >= 32 then begin
GetFreeSysRes := GetProcAddress16(ProcHandle,Pchar('GetFreeSystemResources'));
if assigned(GetFreeSysRes) then begin
ProcThunkH := LoadLibrary(Pchar('kernel32.dll'));
if ProcThunkH >= 32 then begin
QtThunk := GetProcAddress(ProcThunkH,Pchar('QT_Thunk'));
if assigned(QtThunk) then
asm
push SysResource //push arguments
mov edx, GetFreeSysRes //load 16-bit procedure pointer
call QtThunk //call thunk
mov Result, ax //save the result
end;
end;
FreeLibrary(ProcThunkH);

end;
end;
FreeLibrary16(ProcHandle);
end
else Result := 100;
end;
首先,LoadLibrary16等三个API是静态声明的(也可以动态声明,我这么做是为了减少代码)。由于LoadLibrary无法正常调入16位的例程(微软啊!),所以改用 LoadLibrary16、FreeLibrary16、GetProcAddress16,它们与LoadLibrary、FreeLibrary、GetProcAddress的意义、用法、参数都一致,唯一不同的是必须用它们才能正确加载16位的例程。

在定义部分声明了函数指针TGetFreeSysRes 和TQtThunk。Stdcall、cdecl参数定义堆栈的行为,必须根据原函数定义,不能更改。

假如类似一般的例程调用方式,跟踪到这一步:if assigned(GetFreeSysRes) then begin GetFreeSysRes已经正确加载并且有了函数地址,却无法正常使用GetFreeSysRes(int)!!! 
所以这里动态加载(理由也是在win2k下无法执行)了一个看似多余的过程QT_Thunk。对于一个32位的外部例程,是不需要QT_Thunk的, 但是,对于一个16位的例程,就必须使用如上汇编代码(不清楚的朋友请参考Delphi语法资料) 
asm 
push SysResource 
mov edx, GetFreeSysRes 
call QtThunk 
mov Result, ax
end; 
它的作用是将压入参数压入堆栈,找到GetFreeSysRes的地址,用QtThunk来转换16位地址到32位,最后才能正确的执行并返回值! 
以上16位DLL的部分在小仓系列中曾经提到过
Delphi开发DLL常见问题 
字符串参数 
前面曾提到过,为了保证DLL参数/返回值传递的正确性,尤其是为C++等其他语言开发的宿主程序使用时,应尽量使用指针或基本类型,因为其他语言与Delphi的变量存储分配方法可能是不一样的。C++中字符才是基本类型,串则是字符型的线形链表。所以最好将string强制转换为Pchar。
如果DLL和宿主程序都用Delphi开发,且使用string(还有动态数组,它们的数据结构类似)作为导出例程的参数/返回值,那么添加ShareMem为工程文件uses语句的第一个引用单元。ShareMem是Borland共享的内存管理器Borlndmm.dll的接口单元。引用该单元的DLL的发布需要包括Borlndmm.dll,否则就得避免使用string。 
初始化COM库 
如果在DLL中使用了TADOConnection之类的COM组件,或者ActiveX控件,调用时会提示 “标记没有引用存储”等错误,这是因为没有初始化COM。DLL中不会调用CoInitilizeEx,初始化COM库被认为是应用程序的责任,这是Borland的实现策略。
你需要做的是1、引用Activex单元,保证CoInitilizeEx函数被正确调用了
2、在单元级加入初始化和退出代码:
initialization
Coinitialize(nil);
finalization
CoUninitialize;
end.
3、 在结束时记住将连接和数据集关闭,否则也会报地址错误。
在DLL中建立及显示窗体 
凡是基于窗体的Delphi应用程序都自动包含了一个全局对象Application,这点大家是很熟悉的。值得注意的是Delphi创建的DLL同样有一个独立的Application。所以若是在DLL中创建的窗体要成为应用程序的模式窗体的话,就必须将该Application替换为应用程序的,否则结果难以预料(该窗体创建后,对它的操作比如最小化将不会隶属于任何主窗体)。在DLL中要避免使用ShowMessage而用MessageBox。
创建DLL中的模式窗体比较简单,把Application.Handle属性作为参数传递给DLL例程,将该句柄赋与Dll的Application.Handle,然后再用Application创建窗体就可以了。
无模式窗体则要复杂一些,除了创建显示窗体例程,还必须有一个对应的释放窗体例程。对于无模式窗体需要十分小心,创建和释放例程的调用都需在调用程序中得到控制。这有两层意思:一要防止同一个窗体实例的多次创建;二由应用程序创建一个无模式窗体必须保证由应用程序释放,否则假如DLL中有另一处代码先行释放,再调用释放例程将会失败。
下面是DLL窗体的代码:
unit uSampleForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

Dialogs, ExtCtrls, StdCtrls;

type
TSampleForm = class(TForm)
Panel: TPanel;
end;
procedure CreateAndShowModalForm(AppHandle : THandle;Caption : PChar);export;stdcall;
function CreateAndShowForm(AppHandle : THandle):LongInt;export;stdcall;
procedure CloseShowForm(AFormRef : LongInt);export;stdcall;
implementation
{$R *.dfm}

//模式窗体

procedure CreateAndShowModalForm(AppHandle : THandle;Caption : PChar);

var
Form : TSampleForm;
str : string;
begin
Application.Handle := AppHandle;
Form := TSampleForm.Create(Application);
try
str := Caption;
Form.Caption := str;
Form.ShowModal;
finally
Form.Free;
end;
end;
//非模式窗体
function CreateAndShowForm(AppHandle : THandle):LongInt;
var
Form : TSampleForm;
begin
Application.Handle := AppHandle;
Form := TSampleForm.Create(Application);
Result := LongInt(Form);
Form.Show;
end;
procedure CloseShowForm(AFormRef : LongInt);
begin
if AFormRef > 0 then
TSampleForm(AFormRef).Release;
end;
end.
DLL工程单元的引出声明:
exports
CloseShowForm,
CreateAndShowForm,
CreateAndShowModalForm;
应用程序调用声明:
procedure CreateAndShowModalForm(Handle : THandle;Caption : PChar);stdcall;external 'FileOperate.dll';

function CreateAndShowForm(AppHandle : THandle):LongInt;stdcall;external 'FileOperate.dll';

procedure CloseShowForm(AFormRef : LongInt);stdcall;external 'FileOperate.dll';
除了普通窗体外,怎么在DLL中创建TMDIChildForm呢?其实与创建普通窗体类似,不过这次需要传递调用程序的Application.MainForm作为参数:

function ShowForm(mainForm:TForm):integer;stdcall
var
Form1: TForm1;
ptr:PLongInt;
begin
ptr:=@(Application.MainForm);//先把DLL的MainForm句柄保存起来,也无须释放,只不过是替换一下
ptr^:=LongInt(mainForm);//用调用程序的mainForm替换DLL的MainForm
Form1:=TForm1.Create(mainForm);//用参数建立
end;
代码中用了一个临时指针的原因在Application.MainForm是只读属性。MDI窗体的FormStyle不用设为fmMDIChild。
引出DLL中的对象
从DLL窗体的例子中可以发现,将句柄做为参数传递给DLL,DLL能指向这个句柄的实例。同样的道理,从DLL中引出对象,基本思路是通过函数返回DLL中对象的指针,将该指针赋值到宿主程序的变量,使该变量指向内存中某对象的地址。对该变量的操作即对DLL中的对象的操作。
本文不再详解代码,仅说明需要注意的几点规则:
1、 应用程序只能访问对象中的虚拟方法,所以要引用的对象方法必须声明为虚方法;
2、 DLL和应用程序中都需要相同的对象及方法定义,且方法定义顺序必须一致;
3、 DLL中的对象无法继承;
4、 对象实例只能在DLL中创建。
声明虚方法的目的不是为了重载,而是为了将该方法加入虚拟方法表中。对象的方法与普通例程是不同的,这样做才能让应用程序得到方法的指针。

DLL毕竟是结构化编程时代的产物,基于函数级的代码共享,实现对象化已经力不从心。现在类似DLL功能,但对对象提供强大支持的新方式已经得到普遍应用,象接口(COM/DCOM/COM+)之类的技术。进程内的服务端程序从外表看就是一个dll文件,但它不通过外部例程引出应用,而是通过注册发布一系列接口来提供支持。它与DLL从使用上有两个较大区别:需要注册,通过创建接口对象调用服务。可以看出,DLL虽然通过一些技巧也可以引出对象,但是使用不便,而且常常将对象化强制转为过程化的方式,这种情况下最好考虑新的实现方法。

delphi 动态加载dll的更多相关文章

  1. Delphi静态加载DLL和动态加载DLL示例

    下面以Delphi调用触摸屏动态库xtkutility.dll为例子,说明如何静态加载DLL和动态加载DLL. 直接上代码. 1.静态加载示例 unit Unit1; interface uses W ...

  2. 用宏定义封装LoadLibrary,方便的动态加载dll

    同学们动态加载dll的时候是不是感觉挺麻烦的,每次都::LoadLibrary,::GetProcAddress,还要typedef一堆函数.最近闲来无聊,用宏封装了一下,可以少写不少代码,用来也挺方 ...

  3. C# 利用反射动态加载dll

    笔者遇到的一个问题,dll文件在客户端可以加载成功,在web端引用程序报错.解决方法:利用反射动态加载dll 头部引用加: using System.Reflection; 主要代码: Assembl ...

  4. c#实现动态加载Dll(转)

    c#实现动态加载Dll 分类: .net2009-12-28 13:54 3652人阅读 评论(1) 收藏 举报 dllc#assemblynullexceptionclass 原理如下: 1.利用反 ...

  5. unity3d动态加载dll的API以及限制

    Unity3D的坑系列:动态加载dll 一.使用限制 现在参与的项目是做MMO手游,目标平台是Android和iOS,iOS平台不能动态加载dll(什么原因找乔布斯去),可以直接忽略,而在Androi ...

  6. Unity3D的坑系列:动态加载dll

    我现在参与的项目是做MMO手游,目标平台是Android和iOS,iOS平台不能动态加载dll(什么原因找乔布斯去),可以直接忽略,而在Android平台是可以动态加载dll的,有了这个就可以实现代码 ...

  7. C#,动态加载DLL,通过反射,调用参数,方法,窗体

    .net中常会用到动态加载DLL,而DLL中可能包含各种参数.方法.窗体,如何来调用动态加载这些参数.方法.窗体呢? 在C#中,我们要使用反射,首先要搞清楚以下命名空间中几个类的关系: System. ...

  8. 动态加载Dll时,通过Type生成类对象

    原文:动态加载Dll时,通过Type生成类对象 转:http://www.cnblogs.com/zfanlong1314/p/4197383.html "反射"其实就是利用程序集 ...

  9. c#实现动态加载Dll

    原文:c#实现动态加载Dll 原理如下: 1.利用反射进行动态加载和调用. Assembly assembly=Assembly.LoadFrom(DllPath); //利用dll的路径加载,同时将 ...

随机推荐

  1. Mysql通过SQL脚本复制表

    create table if not exists t_dest like t_src 其中,t_src是要复制的原始表,t_dest是要创建的新表.

  2. LPSN获取菌python脚本

    本文转载于https://mp.weixin.qq.com/s?__biz=MzIxNzEzODA5NQ==&mid=2649373408&idx=1&sn=232c2cb36 ...

  3. TOYS(叉积)

    TOYS http://poj.org/problem?id=2318 Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 193 ...

  4. 关于swift语言中导入OC三方类找不到头文件的解决方法

    首先我遇到的问题是这样的: 我之前封装的OC类,我导入现在的swift工程中,然后建立桥接文件,在Swift的控制器中可以找到这个OC写的东西. 但是问题来了,当你使用cocoapods导入的OC三方 ...

  5. [leetcode]215. Kth Largest Element in an Array 数组中第k大的元素

    Find the kth largest element in an unsorted array. Note that it is the kth largest element in the so ...

  6. 利用redis完成自动补全搜索功能(三)

    前面已经完成了分词和自动提示功能,最后把搜索结合在一起,来个完成的案例.当然最好还是用搜索分词解决,这个只是一个临时解决方案. 其实加上搜索很简单,要做的就是3件事 1. 分词的时候,把有用词的id存 ...

  7. MVC仓储执行存储过程报错“未提供该参数”

    今天做的时候出现错误: "过程或函数 'sp_ProcName' 需要参数 '@uid',但未提供该参数. 可是我参数都传了,然后调试也是一样,然后对照参数列表, 后来发现执行的时候还要加入 ...

  8. UI设计初学者如何避免走弯路?

    对于初学UI设计的人而言,可能对UI具体是做什么,或者自己是否能顺利转行胜任这样的岗位存在一定的顾虑,今天我们就来重点说说UI是做什么的,以及想学UI到底要如何避免走弯路,快速的学成. 问题一:UI设 ...

  9. Ui设计流行趋势,对颜色的探讨

    设计风向转换的趋势越来越短,在设计圈中,流行设计的跟新换代更是快.在设计时间越来越短的今天,在经理领导不断催促的时下,如何准确的把握当下的流行趋势,如何在设计之初就能定好设计的基调.这对于还是刚入设计 ...

  10. Laravel 处理 Options 请求的原理以及批处理方案

    0. 背景 在前后端分离的应用中,需要使用CORS完成跨域访问.在CORS中发送非简单请求时,前端会发一个请求方式为OPTIONS的预请求,前端只有收到服务器对这个OPTIONS请求的正确响应,才会发 ...