DELPHI封装对象到DLL
在Delphi中实现窗体的DLL封装
在Delphi中实现窗体的DLL封装
何定华
【期刊名称】《电脑知识与技术》
【年(卷),期】2010(006)014
【摘要】为了实现函数和其它资源的共享,我们可以在Delphi中将窗体封装到DLL中,窗体的DLL封装对不同程序、不同语言进行资源共享非常有意义.
【总页数】3页(P3636-3637,3648)
【作者】何定华
【作者单位】武汉职业技术学院计算机系,湖北,武汉,430074
【正文语种】中文
【中图分类】TP311
【相关文献】
1.在Delphi中实现基于DLL的软件设计方法 [J], 侯珂;高洪尧
2.Delphi中利用Tbitmap实现窗体图形动态打印 [J], 李建武
3.Delphi窗体中动态组件的实现 [J], 王凤广;毕玉龙
4.基于Delphi的DLL封装系统数据支撑模块技术的研究 [J], 王春红;陈继红
5.Delphi中DLL封装业务逻辑的实现 [J], 盛永生;许楠;周晶楠
因版权原因,仅展示原文概要,查看原文内容请购买。
Delphi制作DLL的方法
Delphi制作DLL的方法一 Dl l的制作一般步骤二参数传递三 DL L的初始化和退出清理[如果需要初始化和退出清理]四全局变量的使用五调用静态载入六调用动态载入七在DLL建立一个TF orM八在DLL中建立一个TMDIC hildF orM九示例:十 Del phi制作的Dll与其他语言的混合编程中常遇问题:十一相关资料一 Dll的制作一般分为以下几步:1在一个DL L工程里写一个过程或函数2写一个Ex ports关键字,在其下写过程的名称。
不用写参数和调用后缀。
二参数传递1参数类型最好与win dow C++的参数类型一致。
不要用DE LPHI的数据类型。
2 最好有返回值[即使是一个过程],来报出调用成功或失败,或状态。
成功或失败的返回值最好为1[成功]或0[失败].一句话,与wi ndows c++兼容。
3用stdc all声明后缀。
4最好大小写敏感。
5 无须用far调用后缀,那只是为了与w indow s 16位程序兼容。
三D LL的初始化和退出清理[如果需要初始化和退出清理]1 DL LProc[SysU tils单元的一个P ointe r]是DL L的入口。
在此你可用你的函数替换了它的入口。
但你的函数必须符合以下要求[其实就是一个回调函数]。
如下:pro cedur e Dll Enter Point(dwRe ason: DWOR D);fa r;std call;dwRe ason参数有四种类型:DL L_PRO CESS_ATTAC H:进程进入时DL L_PRO CESS_DETAC H:进程退出时DL L_THR EAD_A TTACH :线程进入时DL L_THR EAD_D ETACH :线程退出时在初始化部分写:D LLPro c :=@DLLE nterP oint;Dl lEnte rPoin t(DLL_PROC ESS_A TTACH);2如Form上有Tdc omCon necti on组件,就Uses Acti vex,在初始化时写一句CoI nitia lize(nil);3 在退出时一定保证Dco mConn ectio n.Con necte d :=False,并且数据集已关闭。
delphi封装DLL总结
dwReason参数有四种类型:
DLL_PROCESS_ATTACH:进程进入时
DLL_PROCESS_DETACH进程退出时
DLL_THREAD_ATTACH 线程进入时
DLL_THREAD_DETACH 线程退出时
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure OpenForm(mainForm:TForm);stdcall;External'project2.dll';
procedure ShowCCC;stdcall;External'project2.dll';
六 调用动态载入
1 建立一种过程类型[如果你对过程类型的变量只是一个指针的本质清楚的话,你就知道是怎么回事了]。如:
type
mypointer=procedure(form:Tform);Far;external;
var
Hinst:Thandle;
showform:mypointer;
四 全局变量的使用
在widnows 32位程序中,两个应用程序的地址空间是相互没有联系的。虽然DLL在内存中是一份,但变量是在各进程的地址空间中,因此你不能借助dll的全局变量来达到两个应用程序间的数据传递,除非你用内存映像文件。
五 调用静态载入
1 客户端函数声名:
1)大小写敏感。
2)与DLL中的声明一样。
3 用stdcall声明后缀。
Delphi-DLL注入
AdjustTokenPrivileges() :调整进程的权限.
function AdjustTokenPrivileges(
TokenHandle: THandle; // 访问令牌的句柄
DisableAllPrivileges: BOOL; // 决定是进行权限修改还是除能(Disable)所有权限
var
hRemoteProcess, hRemoteThread: THandle;
pszLibFileRemote: Pointer;
pszLibAFilename: PwideChar;
pfnStartAddr: TFNThreadStartRoutine;
memSize, WriteSize, lpThreadId: Cardinal;
OpenProcess():打开目标进程,得到目标进程的操作权限,详细参看MSDN
function OpenProcess(
dwDesiredAccess: DWORD; // 希望获得的访问权限
bInheritHandle: BOOL; // 指明是否希望所获得的句柄可以继承
//结构PreviousState的长度,如果PreviousState为空,该参数应为 0
var PreviousState: TTokenPrivileges;
// 指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息
var ReturnLength: DWORD //实际PreviousState结构返回的大小
): Pointer;
WriteProcessMemory():往申请到的空间中写入DLL的文件名
delphi dll 参数
Delphi DLL 参数1. 什么是 Delphi DLL?Delphi是一种基于Object Pascal语言的集成开发环境(IDE),是由Embarcadero Technologies开发的。
Delphi可用于开发各种类型的应用程序,包括桌面应用程序、Web应用程序和移动应用程序。
Delphi中的DLL(动态链接库)是一种可重用的代码库,可以在不同的应用程序中共享和调用。
2. DLL 的优势使用DLL的主要优势在于代码重用和模块化开发。
通过将代码放入DLL中,我们可以将其用于多个应用程序中,避免重复编写相同的代码。
此外,DLL还提供了一种有效的方式来实现模块化开发,使得应用程序的开发和维护更加简单和可靠。
3. Delphi DLL 的参数在Delphi中,DLL的参数是指在调用DLL函数时传递给函数的值或引用。
参数可以是输入参数、输出参数或输入/输出参数。
下面是一些常见的DLL参数类型:•整数参数:在Delphi中,整数参数可以是Byte、ShortInt、Word、SmallInt、Cardinal、Integer等类型。
这些参数可以用于传递整数值,如计数器、索引等。
•浮点数参数:浮点数参数可以是Single、Double、Extended等类型。
这些参数用于传递浮点数值,如坐标、价格等。
•字符串参数:字符串参数可以是AnsiString、WideString、UnicodeString 等类型。
这些参数用于传递文本信息,如文件路径、用户名等。
•指针参数:指针参数用于传递内存地址,可以是PChar、PByte、PInteger 等类型。
通过指针参数,我们可以在DLL函数内部修改传入的参数值。
•数组参数:数组参数可以是静态数组或动态数组。
通过数组参数,我们可以传递一组相关的值,如图像像素、数据集等。
•记录参数:记录参数可以是自定义的记录类型。
通过记录参数,我们可以传递一组相关的字段值,如学生信息、订单详情等。
Delphi中控制Word,xml,dll等操作
Delphi中控制Word,xml,dll等操作DLL的建立与调用[转]动态链接库是一个能够被应用程序和其它的DLL调用的过程和函数的集合体,它里面包含的是公共代码或资源。
由于DLL代码使用了内存共享技术,在某些地方windows也给了DLL一些更高的权限,因而DLL中可以实现一些一般程序所不能实现的功能,如实现windows的HOOK、ISAPI等。
同时,DLL还为不同语言间代码共享提供了一条方便的途径。
因而DLL在编程时应用较为广泛,本文将介绍如何在Delphi 中建立和使用DLL。
一.DLL 库内存共享机制从使用效果看,DLL和unit 很像,它们都可以被别的工程模块所调用,但二者在内部的实现机制上确存在着差别。
如果一个程序模块中用uses语句引用了某个unit,编译程序在编译该模块时,便会连同unit一起编译,并把编译后的可执行代码链接到本程序模块中,这就是一个程序模块能够调用所引用unit中过程和函数的原因。
当同一个unit被多个工程所引用时,则每个工程中都含有该unit的可执行代码,当含有该unit的多个工程同时执行时,unit的可执行代码会随不同工程而多次被调入内存,造成内存资源的浪费。
DLL则不同,它即使被某个工程调用,编译后仍是独立的。
也就是说编译后,一个DLL库形成一个单独的可执行文件,而不与任何其它的可执行文件连接在一起,因而DLL库并不从属于某个特定的工程,当多个工程调用同一个DLL库时只有第一个工程把DLL库调入内存,其余工程并不重复调入同一个DLL库到内存,而是到同一个共享内存区读取。
并且,DLL的执行代码是在程序运行期间动态调入的,而不是如unit在程序运行时就与整个工程一起调入内存。
这样便可消除unit带来的相同代码多处占用内存的弊病。
二 Delphi中DLL库的建立在Delphi环境中,编写一个DLL同编写一个一般的应用程序并没有太大的区别。
事实上作为DLL主体的DLL函数的编写,除了在内存、资源的管理上有所不同外,并不需要其它特别的手段。
Delphi动态与静态调用DLL(最好的资料)
Delphi动态与静态调用DLL(最好的资料)摘要:本文阐述了 Windows 环境下动态链接库的概念和特点,对静态调用和动态调用两种调用方式作出了比较,并给出了 Delphi 中应用动态链接库的实例。
一、动态链接库的概念动态链接库( Dynamic Link Library ,缩写为 DLL )是一个可以被其它应用程序共享的程序模块,其中封装了一些可以被共享的例程和资源。
动态链接库文件的扩展名一般是 dll ,也有可能是 drv 、 sys 和 fon ,它和可执行文件( exe )非常类似,区别在于 DLL 中虽然包含了可执行代码却不能单独执行,而应由 Windows 应用程序直接或间接调用。
动态链接是相对于静态链接而言的。
所谓静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。
换句话说,函数和过程的代码就在程序的 exe 文件中,该文件包含了运行时所需的全部代码。
当多个程序都调用相同函数时,内存中就会存在这个函数的多个拷贝,这样就浪费了宝贵的内存资源。
而动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。
仅当应用程序被装入内存开始运行时,在 Windows 的管理下,才在应用程序与相应的 DLL 之间建立链接关系。
当要执行所调用 DLL 中的函数时,根据链接产生的重定位信息, Windows 才转去执行 DLL 中相应的函数代码。
一般情况下,如果一个应用程序使用了动态链接库, Win32 系统保证内存中只有 DLL 的一份复制品,这是通过内存映射文件实现的。
DLL 首先被调入 Win32 系统的全局堆栈,然后映射到调用这个 DLL 的进程地址空间。
在 Win32 系统中,每个进程拥有自己的 32 位线性地址空间,如果一个 DLL 被多个进程调用,每个进程都会收到该 DLL 的一份映像。
Delphi下DLL调用以及共享数据库连接
p(parameter) //调用Dll.含参数。
else
begin
strpcopy(b,'Method Not Defined: '+modulename+'!');
messagebox(message_parameter.mainhandle,b,
第二种:外部函数引用
做一个单元(unit),引用外部函数。例如:
function myDllCall(var i:integer): Integer; stdcall; external 'mydll.dll' name 'mydllpro';
依赖于DLL接口输出方式,或者写成:
function myDllCall(var i:integer): Integer; stdcall; external 'mydll.dll' index my_index;
process_cancel_service_form.showmodal;
process_cancel_service_form.free;
end;
(******************************)
exports
run_cancel_service;
begin
b,'Error...',mb_ok or mb_iconexclamation);
end;
end;
说明:如果你有一个dll是这么写的,
library cancel_service;
uses
delphi dll 传方法
delphi dll 传方法Delphi是一种广泛使用的编程语言,可以用于开发Windows平台上的应用程序。
在Delphi中,可以使用动态链接库(DLL)来传递方法。
本文将介绍如何在Delphi中使用DLL来传递方法。
什么是DLL?DLL是一种包含可执行代码和数据的文件,它可以被其他程序动态加载和调用。
通过将方法封装在DLL中,我们可以将其作为一个模块在不同的应用程序之间共享和重用。
在Delphi中,可以使用关键字“external”来声明一个外部函数,该函数位于DLL中。
通过这种方式,我们可以在Delphi中调用DLL中的函数,实现方法的传递。
下面是一个简单的示例,演示了如何在Delphi中使用DLL来传递方法。
假设我们有一个DLL文件,其中包含一个名为"Add"的函数,用于将两个整数相加并返回结果。
我们需要在Delphi中声明该函数。
在Delphi的代码编辑器中,我们可以使用以下代码来声明该函数:```delphifunction Add(a, b: Integer): Integer; stdcall; external 'MyDLL.dll';```在这个声明中,我们使用了函数名"Add",并指定了两个整数参数a 和b,返回值为整数。
关键字"stdcall"指定了函数的调用约定,这是DLL函数默认的调用约定。
接下来,我们可以在Delphi的程序中调用这个函数,并传递参数。
下面是一个简单的示例:```delphivara, b, result: Integer;begina := 10;b := 20;result := Add(a, b);ShowMessage('The result is: ' + IntToStr(result));end;```在这个示例中,我们定义了两个整数变量a和b,并将它们分别赋值为10和20。
Delphi中高级DLL的编写和调用
Delphi中高级DLL的编写和调用2001年06月14日10:54:38 赛迪网苏涌根据Delphi提供的有关DLL编写和调用的帮助信息,你可以很快完成一般的DLL编写和调用的应用程序。
本文介绍的主题是如何编写和调用能够传递各种参数(包括对象实例)的DLL。
例如,主叫程序传递给DLL一个ADOConnection 对象示例作为参数,DLL中的函数和过程调用通过该对象实例访问数据库。
需要明确一些基本概念。
对于DLL,需要在主程序中包含exports子句,用于向外界提供调用接口,子句中就是一系列函数或过程的名字。
对于主叫方(调用DLL的应用程序或其它的DLL),则需要在调用之前进行外部声明,即external保留字指示的声明。
这些是编写DLL和调用DLL 必须具备的要素。
另外需要了解Object Pascal 中有关调用协议的内容。
在Object Pascal 中,对于过程和函数有以下五种调用协议:指示字参数传递顺序参数清除者参数是否使用寄存器register 自左向右被调例程是pascal 自左向右被调例程否cdecl 自右向左调用者否stdcall 自右向左被调例程否safecall 自右向左被调例程否这里的指示字就是在声明函数或过程时附加在例程标题之后的保留字,默认为register,即是唯一使用CPU寄存器的参数传递方式,也是传递速度最快的方式;pascal: 调用协议仅用于向后兼容,即向旧的版本兼容;cdecl: 多用于C和C++语言编写的例程,也用于需要由调用者清除参数的例程;stdcall: 和safecall主要用于调用W indows API 函数;其中safecall还用于双重接口。
在本例中,将使用调用协议cdecl ,因为被调用的DLL中,使用的数据库连接是由主叫方传递得到的,并且需要由主叫方处理连接的关闭和销毁。
下面是DLL完整源程序和主叫程序完整源程序。
包括以下四个文件:Project1.DPR {主叫程序}Unit1.PAS {主叫程序单元}Project2.DPR {DLL}Unit2.PAS {DLL单元}{---------- DLL 主程序Project2.DPR ----------}library Project2;usesSysUtils,Classes,Unit2 in 'Unit2.pas' {Form1};{$R *.RES}{ 下面的语句用于向调用该DLL的程序提供调用接口}exportsDoTest; { 过程来自单元Unit2 }beginend.{---------- DLL中的单元Unit2.PAS ----------}unit Unit2;interfaceusesWindows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Db, ADODB, StdCtrls, Menus;typeTForm1 = class(TForm)ADOConnection1: TA DOConnection;{ 本地数据库连接}Memo1: TMemo; { 用于显示信息}privatepublicend;{ 该过程向外提供}procedure DoTest(H: THandle; { 获得调用者的句柄}A Conn: TADOConnection;{ 获得调用者的数据库连接}S: string; { 获得一些文本信息}N: Integer); { 获得一些数值信息}cdecl; { 指定调用协议}implementation{$R *.DFM}procedure DoTest(H: THandle; AConn: TA DOConnection; S: string; N: Integer); beginApplication.Handle := H; { 将过程的句柄赋值为调用者的句柄}{ 上面语句的作用在于,DLL的句柄和调用者的句柄相同,在任务栏中就不会} { 各自出现一个任务标题了。
Delphi下的DLL编程
深入Delphi下的DLL编程作者:岑心引言相信有些计算机知识的朋友都应该听说过―DLL‖。
尤其是那些使用过windows操作系统的人,都应该有过多次重装系统的―悲惨‖经历——无论再怎样小心,没有驱动损坏,没有病毒侵扰,仍然在使用(安装)了一段时间软件后,发现windows系统越来越庞大,操作越来越慢,还不时的出现曾经能使用的软件无法使用的情况,导致最终不得不重装系统。
这种情况常常是由于dll文件的大量安装和冲突造成的。
这一方面说明DLL的不足,另一方面也说明DLL的重要地位,以至我们无法杜绝它的使用。
DLL(动态链接库,Dynamic Link Library)简单来说是一种可通过调用执行的已编译的代码模块。
DLL是windows系统的早期产物。
当时的主要目的是为了减少应用程序对内存的使用。
只有当某个函数或过程需要被使用时,才从硬盘调用它进入内存,一旦没有程序再调用该DLL了,才将其从内存中清除。
光说整个windows系统,就包括了成百上千个dll文件,有些dll文件的功能是比较专业(比如网络、数据库驱动)甚至可以不安装的。
假如这些功能全部要包括在一个应用程序(Application program)里,windows将是一个数百M大小的exe文件。
这个简单的例子很容易解释DLL的作用,而调用DLL带来的性能损失则变得可被忽略不计。
多个应用程序调用同一个DLL,在内存里只有一个代码副本。
而不会象静态编译的程序那样每一个都必须全部的被装入。
装载DLL时,它将被映射到进程的地址空间,同时使用DLL的动态链接并非将库代码拷贝,而仅仅记录函数的入口点和接口。
同时DLL还能带来的共享的好处。
一家公司开发的不同软件可能需要一些公用的函数/过程,这些函数/过程可能是直接的使用一些内部开发的DLL;一些常用的功能则可以直接使用windows 的标准DLL,我们常说的windows API就是包含在windows几个公用DLL文件里的函数/过程;理论上(如果不牵涉作者的版权),知道一个DLL的声明及作用(函数定义的输入参数及返回值),我们完全可以在不清楚其实现(算法或编译方式)的情况下直接使用它。
Delphi 中的dll 封装和调用对象方法
Delphi 中的dll 封装和调用对象方法2011-01-28 22:18动态链接库是一种特殊格式的二进制数据文件,后缀名为.dll。
它可以在程序运行时调用,并不需要进行重新编译,又由于它是完全独立的一个功能模块,使得其重用性非常好。
将dll载入内存后,dll中的功能接口可以被系统中任何正在运行的应用程序所使用。
具有节省空间,升级容易和高重用性等优点。
很多delphi的教材资料上讲到dll,大多都是将用dll来封装一些函数或者过程。
但有时我们想把某个问题当成一个对象来处理,使程序模块化、组件化,方便团队开发、维护和更新。
这时我们就需要用到dll封装和调用对象的技术,然而delphi在封装对象方面有一定的技术难度,资料也比较少,使得很多程序员认为delphi的dll不支持对象封装。
下面简单分享一下我学习dll对象封装和调用的一些心得,希望能对这方面有需求的程序员一些帮助。
首先让我们认识一点,调用dll的程序只能使用与dll中对象动态绑定的方法,理解这一点是实现Dll封装和使用对象的关键。
理解了这一点,我们就会想怎样才能实现与dll中对象的动态绑定呢?Delphi的接口技术为我们提供了一个好的选择,我们可以通过接口类去动态的获取dll中的对象。
下面我用一个简单的demo来说明如何实现。
我建立了这样一个工程组,如下图按如下几步进行:1、先定义一个接口单元IDemo,代码如下:unit IDemo;interfacetypeIMax = interface (IInterface)function Max(num1, num2 : Integer) : Integer;end;implementationend.建立我们需要封装的对象单元UnitMax.pas,注意看TMax继承自哪些父类,代码如下:unit UnitMax;interfaceusesIDemo;typeTMax = class(TInterfacedObject, IMax)function Max( num1, num2 : Integer ) : Integer;end;implementationfunction TMax.Max(num1: Integer; num2: Integer) : Integer;beginResult := 0;if num1 > num2 thenResult := num1elseResult := num2;end;end.3、再在dll单元,输入如下代码:library ProjectDll;usesShareMem,SysUtils,Classes,IDemo in 'IDemo.pas',UnitMax in 'UnitMax.pas',{$R *.res}function MaxObj : IMax;beginResult := TMax.Create;end;exportsMaxObj,end.这样一个提供对象接口的dll就完成了,点击build编译就行了。
delphi dll文件详解
非本人原创,从其他网络上下载而来的一、DLL动态链接库文件的知识简介:Windows的发展要求允许同时运行的几个程序共享一组函数的单一拷贝。
动态链接库就是在这种情况下出现的。
动态链接库不用重复编译或链接,一旦装入内存,Dlls函数可以被系统中的任何正在运行的应用程序软件所使用,而不必再将DLLs函数的另一拷贝装入内存。
任何应用程序都可以共享由装入内存的DLLs管理的内存资源块。
只包含共享数据的DLLs称为资源文件。
在Delphi中,一般工程文件的头标用program关键字,而DLLs工程文件头标用library关键字标识(ActiveX控件也是一样)。
不同的关键字通知编译器生成不同的可执行文件。
用program关键字生成的是.exe 文件,而用library关键字生成的是.dll等其他文件;假如要输出供其它应用程序使用的函数或过程,则必须将这些函数或过程列在Exports子句中。
而这些函数或过程本身必须用export编译指令进行编译。
、使用DLL动态链接库技术主要有以下几个原因:1>、减少可执行文件的大小;2>、实现资源共享;3>、便于维护和升级4>、比较安全二、DLL动态链接库文件的分类:根据DLLs完成的功能,我们把DLLs分为如下的三类:1、完成一般功能的DLLs;2、用于数据交换的DLLs;3、用于窗体重用的DLLs。
三、DLL动态链接库文件的基本格式如下:library Project1;//定义DLL文件的文件名,也是库名。
和Unit差不多,会随保存时的文件名一起改变usesSysUtils,Classes,Unit1in'Unit1.pas'{Form1},//创建的窗体文件Unit2in'Unit2.pas';//创建的单元文件Type//定义自己的数据类型Var//定义变量。
//自己定义的函数function TestDll(i:integer):integer;stdcall;//与平时的编写差不多,只是多了一个stdcall参数beginResult:=i+i;end;{$R*.res}//设置版本信息Project|options,必须有{$R*.res}才能显示。
【DELPHI】利用远程线程注入DLL
【DELPHI】利⽤远程线程注⼊DLLSDK⽂档⾥是这样描述的:进程是⼀个正在运⾏的程序,它拥有⾃⼰的地址空间,拥有⾃⼰的代码,数据和其他系统资源.⼀个进程包含了⼀个或者多个运⾏在此进程内的线程. 从定义上看出进程⼀定要有线程,线程是进程内存中的独⽴实体. 线程插⼊技术就是把⼀个线程弄到别的进程中执⾏的技术。
远程线程插⼊代码之DLL注⼊技术:我们先编写个简单的DLL:library TestDll;usesWindows;{$R *.res}procedure func_a;beginMessageBox(0,'I love delphi','Function form Tset DLL',0);end;procedure func_b(MSG:pchar);beginMessageBox(0,MSG,'Function form Tset DLL',0);end;beginfunc_a;func_b('I like it too!');end.我们弄个程序加载它的⼊⼝点,> 新建⼀个普通程 >加⼀个按钮> 按钮事件只要写⼀句 >loadlibrary('testdll.dll'); > 存为MainShow.dpr运⾏,单击按钮,怎么养弹出东西了吧。
DLL会写了,现在的问题就是怎么注⼊了我们⽬的只是让对⽅的程序运⾏⼀句loadlibrary('testdll.dll');⽽已你可能要问,既然刚才说了远程线程可以直接注⼊⼀个线程,为什么还要多此⼀举反过来再调⽤DLL呢这是因为,远程线程技术⼀般是直接对⽬标程序的内存进⾏操作,我们知道不同程序的虚拟内存是不⼀样的,所以很多函数的地址不⼀定⼀样,⽽程序运⾏的时候实际上是Call函数地址进⾏函数调⽤的;⽽DLL调⽤时其实是被映射到进程内存⾥⾯,DLL拥有⾃⼰的导⼊表,资源,函数等东西,实际上就是⼀个完整的程序,映⼊内存后和执⾏⼀个程序效果是⼀样的这样我们就不⽤考虑那些乱七⼋糟的东西,只要安⼼的写功能即可.我们看看WindowsAPI CreateThread :function CreateThread(lpThreadAttributes: Pointer; //安全指针⼀般nil就可以了dwStackSize: DWORD; //线程初始化尺⼨,⼀般⽤0,获得与主线程⼀样尺⼨(不够⾃⼰会增加,别担⼼)lpStartAddress: TFNThreadStartRoutine; //⼀个指向要执⾏线程函数的指针,这个函数必须遵守stdcall约定,并且可带⼀个参数,参数必须是指针类型lpParameter: Pointer; //函数的参数dwCreationFlags: DWORD;//控制创建标志,⽤0表⽰线程⽴刻执⾏var lpThreadId: DWORD) //返回标识变量我觉得没什么⽤,反正句柄都有了): THandle; //返回线程的句柄stdcall;//标准调⽤ Windows下API⼀般都是标准调⽤看起来似乎⽐较复杂,等下举个例⼦我们把DLL源码⾥⾯的func_b拷到刚才那个EXE上稍微修改下procedure func_b(MSG:pchar); stdcall;beginMessageBox(0,MSG,'Function form Tset DLL',0);sleep(10000);//线程暂停N久(不超过10s)end;加上2个按钮第⼀个procedure TForm1.Button2Click(Sender: TObject);beginfunc_b('123');end;第⼆个procedure TForm1.Button3Click(Sender: TObject);var tid:longword;//放返回值,不放她不让执⾏,郁闷str:pchar;//便于获得pointerbeginstr:='123';createthread(nil,0,@func_b, //函数名前⾯加@是得到函数指针pointer(str),//虽然str也是指针,但是delphi就是要pointer型的,那就转⼀下类型0 , tid);//tid纯属放着占格式的,⼀般我们⽤不到end;//上⾯CreateThread看得懂吧,⼏乎都是默认设置,以后套下去⽤就是了实际上都是调⽤func_b,只是第⼆个过程⽤了信新线程但是效果是不⼀样的第⼀个按钮按下弹出窗⼝后,程序卡死了(暂停10000)第⼆个却不会为什么呢我们可以这样理解窗⼝看做⼀个主线程,执⾏func_b,弹出窗⼝,然后主线程挂起,于是卡死了⽽第⼆个过程创建⼀个新线程,新线程执⾏func_b,弹出窗⼝,挂起10000,但是由于主线程没有挂起,所以看起来关掉窗⼝后没什么事情发⽣(实际上那个线程还在偷偷执⾏,直到线程代码运⾏完,只是它卡死不会影响你)这个如果明⽩了那么下⾯就容易理解了看看这个函数function CreateRemoteThread(hProcess: THandle;lpThreadAttributes: Pointer;dwStackSize: DWORD;lpStartAddress: TFNThreadStartRoutine;lpParameter: Pointer;dwCreationFlags: DWORD;var lpThreadId: DWORD): THandle; stdcall;除了函数名不⼀样,下⾯的参数多了个hProcess: THandle;,剩下的完全⼀样呵呵,这个东西就是本节课的关键了先看函数名就知道是⼲什么⽤的了 '创建远程线程'⽤法和刚才基本⼀致就是hProcess: THandle是什么呢这⾥要填的是被注⼊线进程的句柄什么是句柄打个⽐⽅,对象是⼀个门,句柄就是那个把⼿,通过句柄我们可以对门进⾏操作也就是说我们利⽤句柄来操作某些东西(包括进程,线程等等)你有没有注意到,CreateThread和CreateRemoteThread都返回⼀个THandle,也就是线程的句柄还有loadlibrary也会返回DLL的句柄,我们可以利⽤他们对相关对象进⾏操作那么怎么获得进程句柄呢⼀般采⽤先得到进程PID再⽤下⾯的函数取得句柄function OpenProcess(dwDesiredAccess: DWORD; //访问标志⼀般填写 PROCESS_ALL_ACCESS,这样这个句柄可以获得最⼤操作权限bInheritHandle: BOOL;//可否继承,这个跟⼦程序有关,⽆所谓了,填false和true都可以,反正我们⾃⼰能操作就可以dwProcessId: DWORD): //要获得句柄的进程IDTHandle; stdcall;//返回句柄有时候会返回0,说明打开句柄失败了⼀般是你的权限不够(⽐如你想对Winlogon这些系统级程序操作)这时候我们需要提升权限⼀般Debug权限就可以了(其实操作权限⾥⾯最⾼了)提升的过程我写好了直接调⽤就可以了(修改进程令牌到Debug级别,为什么这样写这⾥不详细讲了,⾃⼰去⽹上搜索下) procedure GetDebugPrivs;varhToken: THandle;tkp: TTokenPrivileges;retval: dword;beginIf (OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken)) thenbeginLookupPrivilegeValue(nil, 'SeDebugPrivilege' , tkp.Privileges[0].Luid);tkp.PrivilegeCount := 1;tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;AdjustTokenPrivileges(hToken, False, tkp, 0, nil, retval);end;end;不会晕吧应该记得我刚才提到了要PID,那怎么得到呢⼀般⽤FindWindow和GetWindowThreadProcessId配合的到这样写先var Pid:longword;//储存那个PIDGetWindowThreadProcessId(FindWindow('Notepad', nil), @PID);这样就找到笔记本的PID,再如'Shell_TrayWnd'可以找到Explorer的窗⼝类名据说可以⽤SPY++查询,不过这东西我没见过,呵呵当然还可以枚举进程判断进程名等等这个先告⼀段落.好了,拿Windows的笔记本下⼿吧procedure TmyForm.Button4Click(Sender: TObject);var h:longword;//PID和THandle 的类型其实都是longword,改个名字⽽已,所以可以通⽤beginwinexec('notepad',1);//运⾏笔记本GetWindowThreadProcessId(FindWindow('notepad', nil), @h);//得到Pid存在hh:=OpenProcess(PROCESS_ALL_ACCESS, False, h);//得到handle存在h,后⾯那个是变量pid,算完放到前⾯的h是句柄(两个不同的东西,只是类型⼀样⽽已)sleep(2000);//等2秒TerminateProcess(h,0);//关闭笔记本,h是那个句柄,0表⽰正常退出end;运⾏起来就是打开⼀个笔记本,⼤约2s后关掉它不知道⼤家看懂了没有,没有不要紧,只是为了证明我们可以拿到⼀个可操作的进程句柄好像万事具备了吧那试试远程线程了吧再建⼀个按钮前⾯的还是这样写,再把那个建⽴线程的拷过来改成CreateRemoteThread加上h参数procedure TmyForm.Button5Click(Sender: TObject);varh:longword;tid:longword;str:pchar;beginstr:='123';winexec('notepad',1);GetWindowThreadProcessId(FindWindow('notepad', nil), @h);h:=OpenProcess(PROCESS_ALL_ACCESS, False, h);CreateRemoteThread(h,nil, 0, @func_b, pointer(str), 0 , tid);end;运⾏起来笔记本出来了,对话框也出来了...可是对话框却不是我们弄的那个,是个报错的看看写了什么内存'0x00000000'不能为'writen'为什么呢记得我刚才说的么远程线程是在别的程序⾥运⾏⼀个线程相当于让⾥⼀个函数执⾏CreateThread所以,函数的地址不⼀定是⼀样的,更何况笔记本⾥⾯怎么可能会有func_b这个我们⾃⼰写的函数呢这么⼀来当然要出错了这下傻了,那怎么注⼊我们要的函数呢记得我们要讲什么吗 -利⽤远程线程进⾏DLL注⼊我们可以把函数写在DLL⾥⾯,⽤远程线程让⽬标进程加载它这样函数就执⾏了我们只要想办法让对⽅程序loadlibrary('testdll.dll');那就OK了看看LoadLibrary的原型function LoadLibrary(lpLibFileName: PAnsiChar): HMODULE; stdcall;你应该发现了它和线程要求的函数格式⼏乎⼀样参数是指针型PAnsiChar就是pchar,⼀个指向字符串的指针返回HMODULE,HMODULE实质是longword(改个名字⽽已)^_^,那就远程运⾏它吧这时候你可能会想,LoadLibrary的地址要怎么得到呢要知道,LoadLibrary是⼀个API(在Kernel32.dll⾥⾯),实际上,每个Win32程序都需要⾥⾯的函数所以,⼤部分程序运⾏代码前会装⼊这个DLL,把⾥⾯的函数映射到⾃⼰的内存了这么⼀来,只要是这个DLL⾥⾯同⼀个函数在所有的进程⾥地址都是⼀样的哈哈,这样就容易了地址我们⼀般⽤GetProcAddressfunction GetProcAddress(hModule: HMODULE; //模块句柄,DLL被加载后就成⽴模块,等下告诉⼤家怎么得到这个lpProcName: LPCSTR//函数在DLL中的导出名LoadLibrary实际上是LoadLibraryA//这个⼤家看看DelphiWindows单元的源码就知道了): FARPROC; stdcall;//返回指针那些类型看得乱乱的吧,不要管他们,在Delphi上不⿏标停在函数上,类型的原型就出来了好了现在是怎么得到那个模块的句柄的问题⽤GetModuleHandlefunction GetModuleHandle(lpModuleName: PChar)//模块名,DLL被加载后就成⽴模块,所以就是DLL的⽂件名了: HMODULE; stdcall;//返回模块句柄好了.知道了这些得到函数地址就容易了GetProcAddress(GetModuleHandle('KERNEL32.DLL'), 'LoadLibraryA');⼀句搞定问题似乎都解决了吧先别⾼兴,不要忘记了,它还带了个参数,就是那个DLL的名字参数类型是⼀个指向字符串地址的指针这个是个⼤问题,⼀来你不能保证别⼈的程序内存⾥有这个字符串⼆来有你也不知道他的位置,这可怎么办呢⾃⼰写!我们把那个字符串写到对⽅内存⾥呵呵,很霸道的⽅法,但的确是个好⽅法不废话了,开始我们⾸先要在⽬标进程申请⼀块内存,以便把那个参数写进去申请内存⽤VirtualAllocEx,看看它的原型function VirtualAllocEx(hProcess: THandle;//⽬标进程句柄,这个不⽤说了吧lpAddress: Pointer;//分配内存位置,⼀般⽤nil,这样会在系统认为最合适的位置分配dwSize: DWORD;//分配的地址范围,也就是⼤⼩了flAllocationType: DWORD;//如何分配地址,⼀般⽤MEM_COMMIT为指定空间提交物理内存flProtect: DWORD//该段内存的保护类型,PAGE_READWRITE表⽰可读可写): Pointer; stdcall;//返回内存地址,哈哈,这就是我们要的那个参数的指针了好了,分配完内存当然是要把我们的数据写过去了这时候需要⽤到WriteProcessMemory来写进程的内存function WriteProcessMemory(hProcess: THandle; //⽬标进程句柄const lpBaseAddress: Pointer;//要写的内存地址,就填我们那个参数的指针lpBuffer: Pointer;//数据的地址,我们把字符串存这⾥,让他拷nSize: DWORD;//要拷贝的数据长度//字符串在Windows定义是以null(就是16进制的0)结尾的//所以长度就是字符串的长度+1var lpNumberOfBytesWritten: DWORD)//返回的什么东西,没什么⽤: BOOL; stdcall; //返回成功或失败我们来写个完整的代码吧procedure TmyForm.Button6Click(Sender: TObject);varh:longword; //放句柄,中间顺便暂放下PIDtmp:longword;//这个专门来占格式收集垃圾DllName:pchar;Mysize:longword;//放字符串长度Parameter:pointer;//放那个参数的指针(位置在⽬标进程内)beginDLLName:='Testdll.dll';Mysize:=strlen(Dllname)+1;winexec('notepad',1);GetWindowThreadProcessId(FindWindow('notepad', nil), @h);h:=OpenProcess(PROCESS_ALL_ACCESS, False, h);Parameter:= VirtualAllocEx(h, nil, Mysize, MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(h, Parameter, Pointer(DllName), MySize, tmp);CreateRemoteThread(h,nil, 0, GetProcAddress(GetModuleHandle('KERNEL32.DLL'), 'LoadLibraryA'), Parameter, 0 , tmp);end;⼜看到那两个熟悉的对话框了哈哈,这么说我们成功了如把那个DLL换成其他的功能,那就...⼀个现成的调⽤代码:procedure Inject(ProcessHandle: longword; EntryPoint: pointer);varModule, NewModule: Pointer;Size, BytesWritten, TID: longword;beginModule := Pointer(GetModuleHandle(nil));//得到模块句柄,nil表⽰得到⾃⾝模块的Size := PImageOptionalHeader(Pointer(integer(Module) + PImageDosHeader(Module)._lfanew + SizeOf(dword) + SizeOf(TImageFileHeader))).SizeOfImage;VirtualFreeEx(ProcessHandle, Module, 0, MEM_RELEASE);NewModule := VirtualAllocEx(ProcessHandle, Module, Size, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);WriteProcessMemory(ProcessHandle, NewModule, Module, Size, BytesWritten); CreateRemoteThread(ProcessHandle, nil, 0, EntryPoint, Module, 0, TID);end;⽤的时候写个⽆参数的函数(遵守标准调⽤)⽐如func吧inject(⽬标句柄,@func);就OK了注意那个func⾥⾯只能有API函数,⾃⼰写的函数都不能调⽤,想⽤就直接写过程进去吧不然会怎样你试试就知道了注意了VirtualAllocExVirtualFreeExCreateRemoteThread在NT下才能⽤转⾃:。
Delphi中的DLL封装和调用对象技术
Delphi中的DLL封装和调用对象技术本文刊登2003年10月份出版的Dr.Dobb's 软件研发第3期刘艺摘要DLL是一种应用最为广泛的动态链接技术但是由于在DLL中封装和调用对象受到对象动态绑定机制的限制使得DLL在封装对象方面有一定的技术难度导致有些Delphi程序员误以为DLL只支持封装函数不支持封装对象本文着重介绍了DLL中封装和调用对象的原理和思路并结合实例给出了多种不同的实现方法关键字动态链接库DLL对象接口虚方法动态绑定类引用面向对象1物理封装与动态链接物理上的封装意味着将程序封装成若干个独立的物理组成部分各部分之间通过动态链接共同完成系统的功能而且各个物理组成部分可以单独维护和编译不影响其他部分要理解物理封装首先要搞清楚静态链接和动态链接在Delphi中如果程序的各个模块分别保存在不同的单元文件中并通过uses指令来互相调用这就是一个典型的静态链接于是各个静态的子例程编译之后连接器从Delphi 编译过的单元或静态库中取出子例程编译代码并添加到执行文件中最终EXE文件包括了程序及其所属单元的所有代码显然静态链接的单元或模块最终以一个独立的物理形式可执行文件存在除了自己编写的单元文件Delphi还自动uses了一些预设的单元如Windows Messages等这些都是静态链接静态链接无法实现物理上的切割和封装而且一旦其中某个单元或模块改动其他所有单元或模块都得随之重新编译和连接用于实现物理切割和封装的bpl包DLL动态链接库或COM+组件都是一种动态链接的形式在动态链接情况中连接器只使用子例程external声明中的信息在执行文件中产生一些数据表格当Windows向内存中装载执行文件时它首先装载所有必需的DLL然后程序才会启动在装载过程中Windows用函数在内存中的地址填充程序的内部表格每当程序调用一个外部函数时它就会使用该内部数据表格直接对DLL代码它当前装载在程序的地址空间中进行调用注意该模式不会涉及两个不同的应用程序DLL已经变成了应用程序的一部分并装载在同一地址空间所有参数的传递都发生在堆栈上与其它任何函数调用一样这里我们不打算讨论DLL的编译因为我们首先想重点介绍Delphi中的DLL封装和调用对象技术2用DLL封装对象DLL Dynamic Link Library动态链接库就目前来讲已经不再是什么新技术读者可以在书店过时的Delphi 书籍里随便找到讨论DLL编程的章节但这些涉及DLL编程的书中几乎都是谈论用DLL来封装函数的实际上大量的程序员也是在使用DLL来封装函数或面向过程的一个模块一个函数集合而在这里我只想讨论如何用DLL来封装对象这可能是读者未曾有过的DLL使用经验但这却是这本完全围绕面向对象编程的书中重要的部分之一或许你能从中发现一些与众不同的实用技巧参见考虑到目前关于DLL的现成资料很多这里我省略了DLL的基本知识和编写方法假设读者已经有了一定的DLL编程基础如果你没有这样的基础建议参阅拙作Delphi6企业级解决方案及应用剖析DLL编程技术一节P271一般来说使用DLL封装对象主要有以下好处y节约内存多个程序可以使用同一个DLL时该DLL只需加载一次而且可以只在使用时加载不用时销毁y使程序代码实现复用这就是说用DLL封装的对象可以重复使用甚至可以让不同的程序语言调用y使程序模块化组件化这样利于团队开发维护和更新方便然而DLL在封装对象方面却有一定的技术难度这方面资料极少甚至有的程序员误以为DLL只支持封装函数不支持封装对象通过研究我们发现DLL在封装对象上主要的限制在于y调用DLL的应用程序只能使用DLL中对象的动态绑定的方法y DLL封装对象的实例只能在DLL中创建y在DLL和调用DLL的应用程序中都需要对封装的对象及其被调用的方法进行声明下面我先通过一个简单的例子来演示如何使用DLL封装对象并在应用程序中调用该对象然后再讨论相关的技术细节3一个简单的例子读者一定还记得我们在前面章节中演示了车的继承关系和合成关系这个程序由逻辑单元的Demo和界面单元frmDemo组成我们现在就用DLL封装Demo单元的所有对象并在frmDemo单元实现调用读者可以通过这个具体的例子来学习如何使用DLL封装对象打开项目文件ObjDemo.dpr如图1所示在项目管理器Project Manager中鼠标右击ProjectGroup1然后在弹出菜单中选择Add New Project...菜单项此时弹出如图2所示的New Items对话框选择DLL Wizard Delphi的DLL向导将创建一个DLL项目我们将该项目重新命名为DemoSvr并保存在项目组同一目录下图1鼠标右击ProjectGroup1在弹出菜单中选择Add New Project...菜单项图2在New Items对话框中选择DLL Wizard修改DemoSvr中的代码如示例程序1所示示例程序 1 动态链接库DemoSvr的主程序library DemoSvr;{ Important note about DLL memory management: ShareMem must be thefirst unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL--even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. }usesShareMem,SysUtils,Classes,Demo in 'Demo.PAS';{$R *.res}function CarObj:TCar;beginResult:=TCar.create;end;function BicycleObj:TBicycle;beginResult:=TBicycle.create;end;exportsCarObj,BicycleObj;end.由此可见 DLL封装对象的实例是在DLL中创建的CarObj 和BicycleObj函数创建并输出了Car对象和Bicycle对象的引用这样DemoSvr动态链接库就可以通过CarObj 和BicycleObj函数输出Car对象和Bicycle对象了但是Car对象和Bicycle对象是在Demo.pas 文件中声明和实现的所以这里uses了Demo.PAS为了能够使用Demo.PAS在项目管理器中直接把Demo.pas文件从ObjDemo项目中拖放到DemoSvr项目中如图3所示图 3 将Demo.pas文件从ObjDemo项目中拖放到DemoSvr项目中打开Demo.pas修改TBicycle和TCar的声明如下TBicycle = class(TVehicle)publicconstructor create;destructor Destory;procedure ride;virtual;end;TCar = class(TVehicle)protectedFEngine: TEngine;publicconstructor create;destructor Destory;procedure drive;virtual;end;请注意这里我把应用程序中需要调用的对象方法ride和drive改成了虚方法显然这么做不是为了让TBicycle 和TCar的派生类来覆盖ride和drive方法这是因为编译连接应用程序时编译器无法知道也无需知道对象在DLL中的方法是如何实现的这就意味着对于应用程序来说要使用动态绑定晚绑定技术所以调用DLL的应用程序只能使用DLL中对象的动态绑定的方法前面我们讲过虚方法的动态绑定技术是把虚方法的入口放到虚方法表VMT中VMT是一块包含对象方法指针的内存区通过VMT调用程序可以得到虚方法的指针如果我们不把ride和drive声明为虚方法VMT中就不会有这些方法的入口指针因此调用程序也就无法得到这个方法的入口指针接下来回到frmDemo单元在调用DLL的应用程序中同步声明需要调用的的对象及其被调用的方法这里除了将ride和drive声明为虚方法外还要声明为抽象方法因为frmDemo单元不提供ride和drive方法的实现不把它们声明为抽象方法则编译时无法通过应用程序frmDemo单元的完整代码如示例程序2所示示例程序2调用DLL对象的应用程序unit frmDemo;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;type//---这里声明需要用到的DLL中对象的方法---TVehicle = class(TObject);TCar = class(TVehicle)publicprocedure drive;virtual;abstract;end;TBicycle = class(TVehicle)publicprocedure ride;virtual;abstract;end;//----------------------------TForm1 = class(TForm)Button1: TButton;Button2: TButton;procedure Button2Click(Sender: TObject);procedure Button1Click(Sender: TObject);private{ Private declarations }public{ Public declarations }end;varForm1: TForm1;//---这里导入DLL文件及其函数---function CarObj:TCar ;external 'DemoSvr.dll';function BicycleObj:TBicycle ;external 'DemoSvr.dll';implementation{$R *.dfm}procedure TForm1.Button2Click(Sender: TObject);var MyCar:TCar;beginMyCar:=CarObj;if Mycar=nil then exit;tryMyCar.drive;finallyMyCar.Free;end;end;procedure TForm1.Button1Click(Sender: TObject);var Bicycle:TBicycle;beginBicycle:=BicycleObj;tryBicycle.ride;finallyBicycle.Free;end;end;end.最后选择Build All Projects菜单项编译和连接所有的项目如图4所示我们就得到了需要的应用程序可执行文件以及DLL运行测试可以看到这个程序实现了和原先一样的功能但是我对这样的DLL封装对象实现不是太满意因为在DLL和应用程序中都需要声明封装的对象还要使用好virtual和abstract限定符很容易造成阅读程序理解上的错觉如果一旦对象发生变化就需要分别在两边修改对象声明以保持同步稍有不慎就会出错对此Steve Teixeira在Delphi6开发人员指南机械工业出版社2003年出版相关内容参见该书209页一书中提出了使用头文件的方法并通过加上编译指令来控制DLL和应用程序分别读到不同的头文件内容这个方法虽然可以通过只修改头文件来保持声明的同步但编译指令和头文件使得阅读程序更加困难在这里我有一个更好的方法供读者分享这就是使用接口的方法4利用Delphi接口实现DLL中对象的动态绑定前面我们分析过调用DLL的应用程序只能使用DLL中对象的动态绑定的方法理解这一点是实现DLL封装和使用对象的关键那么Delphi接口技术为我们提供了一个最佳选择图 4 选择Build All Projects菜单项编译和连接所有的项目为此我们创建一个接口单元IDemo分别声明ICar和IBicycle接口接口方法分别是应用程序要用到的Drive和Ride完整代码如示例程序3所示示例程序3接口单元IDemo的代码unit IDemo;interfacetypeICar = interface (IInterface)['{ED52E264-6683-11D7-B847-001060806215}']procedure Drive;end;IBicycle = interface (IInterface)['{ED52E264-6683-11D7-B847-001060806216}']procedure Ride;end;implementationend.注意接口单元IDemo中没有也不能有任何实现它同时被应用程序和DLL所用Use这样当需要修改应用程序调用的对象方法时只要在一个地方即该接口单元修改即可避免了可能出现的声明不一致错误使用接口还带来了更多的好处首先无需使用virtual和abstract限定符修改对象方法声明避免了程序阅读上的错觉其次利用接口实例计数器自动管理对象的生命期避免了程序员遗忘销毁对象造成的内存泄漏为了使用接口我将Demo单元的类型声明部分作了以下修改以便TBicycle和TCar 类能够实现接口方法值得高兴的是该单元仅仅需要修改声明部分而程序实现部分根本不需要做任何改动unit Demo;interfaceusesSysUtils, Windows, Messages, Classes, Dialogs,IDemo;typeTVehicle = class(TInterfacedObject)protectedFColor: string;FMake: string;FTopSpeed: Integer;FWheel: TWheel;FWheels: TList;procedure SlowDown;procedure SpeedUp;procedure Start;procedure Stop;end;TBicycle = class(TVehicle,IBicycle)publicconstructor create;destructor Destory;procedure ride;end;TCar = class(TVehicle,ICar)protectedFEngine: TEngine;publicconstructor create;destructor Destory;procedure drive;end;最后检查一下项目管理器确保在应用程序项目和DLL项目中都添加了IDemo单元如图5所示图5确保在应用程序项目和DLL项目中都添加了接口单元IDemo示例程序4使用接口技术调用DLL对象的应用程序unit frmDemo;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, IDemo;//在这里Use IDemo单元typeTForm1 = class(TForm)Button1: TButton;Button2: TButton;procedure Button2Click(Sender: TObject);procedure Button1Click(Sender: TObject);private{ Private declarations }public{ Public declarations }end;varForm1: TForm1;function CarObj:ICar ;external 'DemoSvr.dll';function BicycleObj:IBicycle ;external 'DemoSvr.dll';implementation{$R *.dfm}procedure TForm1.Button2Click(Sender: TObject);var MyCar:ICar;beginMyCar:=CarObj;MyCar.drive;Mycar:=nil;end;procedure TForm1.Button1Click(Sender: TObject);var Bicycle:IBicycle;beginBicycle:=BicycleObj;Bicycle.ride;Bicycle:=nil;end;end.在示例程序4中改动不是很多这里Use了IDemo单元而没有额外的声明实现部分通过接口调用了DLL中的接口方法也可以说是对象方法运行示例程序4和运行示例程序2实现的功能完全一样5使用抽象类实现DLL中对象的动态绑定既然DLL中封装和调用对象受到了对象动态绑定机制的限制那么除了利用Delphi接口技术外我们还可以考虑使用抽象类来实现DLL中对象的动态绑定机制图6显示了一个基于数据库应用的示例程序的面向对象设计我将界面部分设计成一个瘦客户机的形式这是一个供用户交互的可执行文件distributabel2.exe它封装了外观类TfrmUsers我把业务部分包括数据模块设计成提供服务的服务器这是一个动态链接库文件UserSvr.dll它封装了业务类TuserMaint和数据库访问类TuserDM这种设计体现了界面和业务分离的思想o reateTT T o create T o pe DataModuleCreate(..)T o pe btnExitClick(..)T Tf rmUsersu TIUserMaintu TUserDMu TUserMaintu TIUserMaintu objUsersUserDM图 6界面和业务的物理分离的设计由于原来的逻辑独立的类和代码存放在不同的单元文件中我们很容易重新将它们划分到不同的项目里如图 7所示图 7重新将逻辑独立的单元文件划分到不同的项目里瘦客户机其实上就是一个空壳只提供交互的界面它的外观类TfrmUsers 向TUserMaint 的实例对象请求服务该对象封装在DLL 中前面我已经讲过如何用DLL 来封装对象除了前面讲过的两种方法外这里我想介绍第三种方法即使用抽象类做接口的方法由于调用DLL的应用程序只能使用DLL中对象的动态绑定的方法我们不妨专门设计一个抽象类TIUserMaint作为提供对象方法的接口在抽象类TIUserMaint中有供应用程序使用的对象方法不过它们都是虚抽象方法目的是支持动态绑定而又无需提供实现我将新增的TIUserMaint放在抽象类接口单元uIUserMaint.pas文件中其源代码如示例程序5所示这个单元将作为接口文件分别包含在UserSvr和Distributable2项目中如图7所示示例程序5抽象类接口单元uIUserMaint代码unit uIUserMaint;interfaceusesClasses;typeTIUserMaint = class (TObject)publicfunction GetDepList: TStrings;virtual;abstract;function GetUserList(strName:String): OLEVariant;virtual;abstract;procedure UpdateUserData(UserData:OleVariant; out ErrCount: Integer);virtual;abstract;constructor create;virtual;abstract;end;TIUserMaintClass=class of TIUserMaint;implementation//没有实现代码end.在示例程序5中还定义了TIUserMaintClass 类型它是TIUserMaint的类引用这对于把实现类从DLL传递到进行调用的应用程序是必要的一般抽象类只定义接口它由虚抽象方法组成而没有实际的数据为了实现抽象类TIUserMaint的抽象方法原来的TUserMaint类需要继承TIUserMaint类并覆盖其所有的虚抽象方法新的TUserMaint类声明如下TUserMaint = class (TIUserMaint)privateUserDM:TUserDM;publicfunction GetDepList: TStrings;override;function GetUserList(strName:String): OLEVariant;override;procedure UpdateUserData(UserData:OleVariant; out ErrCount: Integer);override;constructor create;override;destructor Destroy;override;end;但实际上TUserMaint类原有的实现部分并不需要改动所以我们的工作量不大示例程序6是动态链接库UserSvr.dll的源代码这里我使用了TObjUsers函数该函数返回了一个类型为TIUserMaintClass的类引用而不是对象引用所以在应用程序中可以使用这样的代码来创建DLL封装的对象objUsers:=TObjUsers.Create;但这不意味着TObjUsers是一个类记住这里TObjUsers是一个DLL输出的函数它的返回类型是一个类引用类型示例程序 6 动态链接库UserSvr.dll的源代码library UserSvr;usesShareMem,SysUtils,Classes,uUserMaint in 'uUserMaint.pas',udmUser in 'udmUser.pas' {UserDM: TDataModule},uIUserMaint in 'uIUserMaint.pas';{$R *.res}function TObjUsers:TIUserMaintClass;beginresult:=TUserMaint;end;exportsTObjUsers;beginend.细心的读者可能已经发现既然TObjUsers函数的返回类型为TIUserMaintClassTIUserMaintClass在示例程序5中声明为:TIUserMaintClass=class of TIUserMaint;那么result:=TUserMaint会不会是写错了呢没有写错我们在应用程序中声明了TIUserMaint类型的对象 objUsers通过传递类引用那条objUsers:=TObjUsers.Create语句实现的是objUsers:= TUserMaint.Create功能这里面隐含了TUserMaint向TIUserMaint转型的过程当然TIUserMaint作为抽象类本身也无法直接创建自己的实例所以必须通过转型才行另外在TIUserMaint的派生类中可以随意改变方法的实现却不会影响到方法的接口这就是说你以后通过进一步修改DLL封装对象的实现方法来升级DLL无需重新修改和编译应用程序因为TIUserMaint作为抽象类提供的方法接口没有改变示例程序7是应用程序实现界面和业务的物理分离后的界面单元的源代码这里要注意几点y在interface部分要Uses抽象类接口单元uIUserMainty objUsers声明为TIUserMaint类型y声明DLL函数function TObjUsers:TIUserMaintClass; external 'UserSvr.dll';除此之外几乎不需要进行其他的改动由此可见从界面和业务的逻辑分离演化到界面和业务的物理分离实际上并不是想象的那样困难示例程序7物理分离后的界面单元代码unit ufrmUsers;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, DB, DBClient, StdCtrls, DBCtrls, Grids, DBGrids, Mask, ExtCtrls, Buttons,uIUserMaint;typeTfrmUsers = class(TForm)btnExit: TButton;btnQryByName: TSpeedButton;Label1: TLabel;Label2: TLabel;Label3: TLabel;Label5: TLabel;Label6: TLabel;Label7: TLabel;edtQryByName: TLabeledEdit;DBEdit1: TDBEdit;DBEdit2: TDBEdit;DBEdit3: TDBEdit;DBEdit4: TDBEdit;DBGrid1: TDBGrid;dbcbSex: TDBComboBox;dbcbDep: TDBComboBox;DataSource1: TDataSource;cdsUserMaint: TClientDataSet;cdsUserMaintID: TWideStringField;cdsUserMaintNAME: TWideStringField;cdsUserMaintSEX: TWideStringField;cdsUserMaintJOB: TWideStringField;cdsUserMaintTEL: TWideStringField;cdsUserMaintCALL: TWideStringField;cdsUserMaintDEP: TWideStringField;cdsUserMaintGROUP_ID: TWideStringField;cdsUserMaintPASSWORD: TWideStringField;btnUpdate: TBitBtn;procedure btnUpdateClick(Sender: TObject);procedure btnQryByNameClick(Sender: TObject);procedure FormCreate(Sender: TObject);procedure btnExitClick(Sender: TObject);procedure FormDestroy(Sender: TObject);privateobjUsers:TIUserMaint;public{ Public declarations }end;varfrmUsers: TfrmUsers;constM_TITLE='操作提示';//所有提示对话框的标题implementation{$R *.dfm}function TObjUsers:TIUserMaintClass;external 'UserSvr.dll';procedure TfrmUsers.btnUpdateClick(Sender: TObject); varnErr:integer;beginif cdsUserMaint.State=dsEdit then cdsUserMaint.Post; if (cdsUserMaint.ChangeCount > 0) thenbeginobjUsers.UpdateUserData(cdsUserMaint.Delta,nErr);if nErr>0 thenapplication.MessageBox('更新失败',M_TITLE,MB_ICONWARNING)elsebeginapplication.MessageBox('更新成功',M_TITLE,MB_ICONINFORMATION) ;btnQryByNameClick(nil);end;end;end;procedure TfrmUsers.btnQryByNameClick(Sender: TObject);beginbtnUpdate.Enabled:=true;dbcbDep.Items.AddStrings(objUsers.GetDepList);cdsUserMaint.Active:=false;cdsUserMaint.Data:=objUsers.GetUserList(edtQryByName.Text);cdsUserMaint.Active:=True;end;procedure TfrmUsers.FormCreate(Sender: TObject);beginobjUsers:=TObjUsers.Create;end;procedure TfrmUsers.btnExitClick(Sender: TObject);beginclose;end;procedure TfrmUsers.FormDestroy(Sender: TObject);beginobjUsers.Free;end;end.作者简介刘艺海军工程大学副教授美国Borland公司授予的 Delphi产品专家计算机技术作家著有10多部计算机专著出版重点大学计算机教材2部这篇文章摘编自他的新书Delphi面向对象编程思想作者个人网站 。
Delphi中Dll的用法
1、DLL简介DLL是Dynamic-Link Libraries(动态链接库)的缩写,库里面是一些可执行的模块以及资源(如位图、图标等)。
可以认为DLL和EXE基本上是一回事,只是DLL不能直接执行,而必须由应用程序或者其他DLL调用。
DLL为应用程序间的资源共享提供了方便,同时也是多语言混合编程的重要手段。
由此可见学习使用DLL是Windows程序员必须掌握的一项重要技术。
2、如何调用DLL在Delphi中有两种方法调用DLL中的函数和过程,即外部声明或者动态加载。
<1> 外部声明在Delphi中外部声明是访问外部例程最容易和最常用的方式,有两种声明方式:通过名字、通过索引号。
举例如下:在MYDLL.DLL中有两个函数和一个过程,则其外部声明可以写成:function test1:integer;external 'mydll ';//直接通过名称调用test1(注意名称大小写敏感)。
function test11:integer;external 'mydll ' name 'test1 ';//通过名称调用test1,在程序中使用新名称(原名称仍然大小写敏感)。
procedure test2;external 'mydll ' index 1;//通过索引号调用TEST2。
程序中可以用与DLL中不一样的名称.使用外部声明的缺点是程序启动时如果找不到mydll.dll将无法运行,即使没有调用其中的模块。
动态加载的方法可以避免这种情况。
<2> 动态加载通过调用Windows API中的相关函数,将DLL调入内存并获得指向函数或过程的指针,执行完模块后释放内存。
除了节约内存外,这种方法的一个很大的优点是能处理找不到dll或者在装入过程中出错的情况。
这样即使某个dll有问题,应用程序的其他部分仍然能够正常运行。
FastReport4.9.32 + Delphi7 封装DLL
FastReport4.9.32 + Delphi7 封装DLL1在盘点清单项目里添加一个新DLL项目,命名为“frxFastReport”2新项目中添加接口单元(uIFrtFastReport)、及实现单元(uFrtFastReport),一个Form窗口(uRPDesigner)。
文件结构如下图。
3 frmRPDesigner窗口的组件清单。
4 接口文件的详细代码如下:定义了两过程:一个是显示报表(ShowReport),一个是设计报表(DesignReport).5 接口的实现:6 fr3DM(TdataModule)单元在本例中没有被使用,可使用去掉。
8 frxFastReport.DLL项目源文件中添加:二、EXE程式调用DLL1 ListTagPrint.exe 程式调用frxFastReport.dll在EXE项目中的主FORM中引用引用接口单元文件(uIFrxFastReport.pas)。
打印按钮事件代码:EXE的主窗体关闭,释放资源。
效果图:计划窗口:简单报表完成了。
异常提示:Project TagListPrint.exe raised exception class EconvertError with message ‘Cannot assign a TfieldList to a TstringList’. Processe stopped. Use Step or Run to continue.解决方式:1)修改frxDBSet.pas原代码文件,把“FDS.GetFieldNames(List);”注释掉。
添加两行“for i:=0 to FDS.FieldCount -1 do List.Add(FDS.Fields[i].FullName) ”重新编辑frxFastReport.DLL项目。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
使用接口避免了将 TCar 在两处声明的难堪,避免了使用 virtual 和 abstract 造成的视觉 混乱,而且接口可以自动管理对象的生命期,避免程序员忘记销毁对象造成的内存泄漏。
总之,使用接口很好的解决了在 DLL 封装对象的局限。
方法三:用 COM/COM+封装对象
Uses ShareMem; …… function CrtCar:TCar; begin
result := TCar.Create; end;
exports CrtCar;
End. 4、 新建一个单元,添加到 DemoDll 项目中,命名为 CarI。代码如下:
unit CarI; interface type
uses ShareMem …… function CrtCar:TCar; begin result:=TCar.Create; end;
exports CrtCar;
5、 在 Frm 单元做以下修改: 添加
TCar = class(TObject) procedure Drive;virtual;abstract;
方法三:用 COM/COM+封装对象..................................................................................................4
方法一:基本封装
原型
原型说明
TForm1 和 TCar 分别在不同的单元当中; TForm1 中有一个按钮 Button1,点击 Button1 执行 TCar.Drive; TForm1 单元 uses 了 TCar 单元; TForm1 的处理过程如下:
var mycar:TCar;
begin mycar:=TCar.Create; mycar.Drive; mycar.Free;
封装 TCar 到 DLL 中
在 DLL 中封装对象的限制
调用 DLL 的应用程序只能使用 DLL 中对象得动态绑定方法。也就是说方法后面必须有 virtual 关键字;
改为 TCar = class(TInterfacedObject,ICar)
6、 Frm 单元代码: procedure TForm1.Button1Click(Sender: TObject); var mycar:ICar; begin mycar:=CrtCar; mycar.Drive; mycar:=nil; end;
方法二:运用接口在 DLL 中封装对象
原型
还是方法一中的原型。
封装步骤
1、 在 Project Manager 中添加新项目 Add New Project,选择 DLL Wizard,命名新项目 为 DemoDll。
2、 将 TCar 添加到 DemoDll 项目中,并在 DemoExe 中删除 Tcar。 3、 在 Library DemoDll 中添加:
end; …… var
function CrtCar:TCar;external 'DemoDll.dll'; 修改
将 mycar:=TCar.Create 改为 mycar:=CrtCar
我们发现,在 TCar 单元中声明了类 TCaபைடு நூலகம்,在 Frm 单元也声明了 TCar,重复声明不是 OO 的正确思想。而且,两处的声明还不太一样,难以理解。
ICar = Interface(IInterface) ['{… …}'] //GUID,按 Ctrl+Shift+g 可以生成 procedure Drive;
end; implementation end. 5、 将 CarT 单元中的 TCar 做一些改变:
将 Tcar = class(TObject)
封装对象的实例只能在 DLL 中创建; 在 DLL 和调用 DLL 的应用程序中都需要对封装对象以及被调用的方法进行声明;
封装步骤
1、 在 Project Manager 中添加新项目 Add New Project,选择 DLL Wizard,命名新项目 为 DemoDll。
2、 将 TCar 单元加入到 DemoDll 项目中,并从原项目 DemoExe 中删除 TCar 单元。 3、 在 TCar 单元做以下修改:在 Drive 方法后面加上 virtual 关键字。 4、 在 Library DemoDll 中做以下修改: 添加
在 Delphi 中封装对象到 DLL
方法一:基本封装.............................................................................................................................1 原型............................................................................................................................................. 1 原型说明..................................................................................................................................... 1 封装 TCar 到 DLL 中.................................................................................................................2 在 DLL 中封装对象的限制...............................................................................................2 封装步骤.............................................................................................................................2