Why we need interfaces in Delphi
http://sergworks.wordpress.com/2011/12/08/why-we-need-interfaces-in-delphi/
Why we need interfaces in Delphi.
Objects are normally accessed by an object reference.
Interface reference is a different method to access an object’s functionality.
A simple question – why do we need interface references at all, why can’t we use object references everywhere?
There are several reasons to use interface references instead of object references,
but most important of them (at least historically) is accessing an object created in a different program module.
Let us consider a simple example – an object is created in .dll module and consumed in .exe module.
The TMathObject class implements Square and Cube functions on the FOperandfield;
we start with the following naive code:
unit MathUnit; interface type
TMathObject = class
private
FOperand: Double;
public
function Square: Double;
function Cube: Double;
property Operand: Double read FOperand write FOperand;
end; implementation function TMathObject.Square: Double;
begin
Result:= Sqr(FOperand);
end; function TMathObject.Cube: Double;
begin
Result:= Sqr(FOperand) * FOperand;
end; end.
We want to create and destroy TMathObject instances in dll module:
library MathDll; uses
MathUnit in 'MathUnit.pas'; function CreateObject: TMathObject;
begin
Result:= TMathObject.Create;
end; procedure FreeObject(Obj: TMathObject);
begin
Obj.Free;
end; exports
CreateObject, FreeObject; {$R *.res} begin
end.
and use an instance of TMathObject in exe module:
program MathTest;
{$APPTYPE CONSOLE}
uses
MathUnit in 'MathUnit.pas';
function CreateObject: TMathObject; external 'MathDll.dll';
procedure FreeObject(Obj: TMathObject); external 'MathDll.dll';
var
MathObj: TMathObject;
begin
MathObj:= CreateObject;
MathObj.Operand:= ;
Writeln('Square = ', MathObj.Square::, '; Cube = ', MathObj.Cube::);
FreeObject(MathObj);
Write('Press ''Enter'' key ... ');
Readln;
end.
If you compile the above example you can see it works, but TMathObject implementation (MathUnit.pas) is duplicated
in both program modules (MathTest.exe and MathDll.dll), and that is not just a waste of program memory.
One of the main reasons to split a program into program modules is a possibility to modify the modules separately;
for example to modify and deploy a different .dll version while keeping an .exe module intact.
In the above example the implementation of TMathObject is a contract that both sides (exe and dll) should adhere,
so the implementation of TMathObject can’t be changed in dll module only.
We need a different form of contract that does not include an object’s implementation.
A possible solution is to introduce a base class containing virtual abstract methods only:
unit BaseMath; interface type
TBaseMathObject = class
protected
function GetOperand: Double; virtual; abstract;
procedure SetOperand(const Value: Double); virtual; abstract;
public
function Square: Double; virtual; abstract;
function Cube: Double; virtual; abstract;
property Operand: Double read GetOperand write SetOperand;
end; implementation end.
unit MathUnit; interface uses BaseMath; type
TMathObject = class(TBaseMathObject)
private
FOperand: Double;
protected
function GetOperand: Double; override;
procedure SetOperand(const Value: Double); override;
public
function Square: Double; override;
function Cube: Double; override;
end; implementation function TMathObject.GetOperand: Double;
begin
Result:= FOperand;
end; procedure TMathObject.SetOperand(const Value: Double);
begin
FOperand:= Value;
end; function TMathObject.Square: Double;
begin
Result:= Sqr(FOperand);
end; function TMathObject.Cube: Double;
begin
Result:= Sqr(FOperand) * FOperand;
end; end.
The library module source code now is
library MathDll; uses
BaseMath in 'BaseMath.pas',
MathUnit in 'MathUnit.pas'; function CreateObject: TBaseMathObject;
begin
Result:= TMathObject.Create;
end; procedure FreeObject(Obj: TBaseMathObject);
begin
Obj.Free;
end; exports
CreateObject, FreeObject; {$R *.res} begin
end.
program MathTest;
{$APPTYPE CONSOLE}
uses
BaseMath in 'BaseMath.pas';
function CreateObject: TBaseMathObject; external 'MathDll.dll';
procedure FreeObject(Obj: TBaseMathObject); external 'MathDll.dll';
var
MathObj: TBaseMathObject;
begin
MathObj:= CreateObject;
MathObj.Operand:= ;
Writeln('Square = ', MathObj.Square::, '; Cube = ', MathObj.Cube::);
FreeObject(MathObj);
Write('Press ''Enter'' key ... ');
Readln;
end.
We can see that MathTest project does not contain MathUnit.pas unit, and is not dependent on TMathObject implementation;
in fact MathTest project does not know that TMathObject class even exist.
We can change TMathObjectimplementation in dll module as much as we want provided that we keepTBaseMathObject intact,
inherit TMathObject from TBaseMathObject and override TBaseMathObject‘s virtual abstract methods.
We implemented a general concept of interface in the form of pure abstract class.
Pure abstract classes are a way how interfaces are implemented in C++ .
This approach has a limited value in Delphi because Delphi does not support multiple inheritance,
and a Delphi class can have only one contract in the form of base abstract class.
Another problem is a limited use of ‘is’ and ‘as’ operators for an object created in a different program module:
Starting from version 3 Delphi introduces a concept of interface that is different from a pure abstract class
and solves the problems with object’s export by using interface references instead of object references:
unit BaseMath; interface type
IBaseMath = interface
['{92E9AFF4-25B7-41BD-9EB6-557D12F98BE6}']
function GetOperand: Double;
procedure SetOperand(const Value: Double);
function Square: Double;
function Cube: Double;
property Operand: Double read GetOperand write SetOperand;
end; implementation end.
There is no need to inherit TMathObject class from a given base class now;
we can inherit TMathObject class from any class we like.
Since all Delphi interfaces are descendants of IUnknown (also nicknamed as IInterface in Delphi)
we should also implement the methods of IUnknown interface in TMathObject class.
Delphi provides a helper TInterfacedObject class that already implements the methods of IUnknown
and can be used as TMathObject ancestor:
unit MathUnit; interface uses BaseMath; type
TMathObject = class(TInterfacedObject, IBaseMath)
private
FOperand: Double;
protected
function GetOperand: Double;
procedure SetOperand(const Value: Double);
public
function Square: Double;
function Cube: Double;
end; implementation function TMathObject.GetOperand: Double;
begin
Result:= FOperand;
end; procedure TMathObject.SetOperand(const Value: Double);
begin
FOperand:= Value;
end; function TMathObject.Square: Double;
begin
Result:= Sqr(FOperand);
end; function TMathObject.Cube: Double;
begin
Result:= Sqr(FOperand) * FOperand;
end; end.
There is no need for FreeObject procedure now.
The FreeObject procedure was introduced in the previous examples
to enforce that a TMathObject instance is destroyed
in the same program module where it was created (i.e. in .dll module).
It is always a good rule of thumb that the one who creates an object is the one who destroys it.
But now there is no need to enforce it – if we use interface references object instances
are automatically destroyed in the same program module where they were created.
library MathDll; uses
BaseMath in 'BaseMath.pas',
MathUnit in 'MathUnit.pas'; function CreateObject: IBaseMath;
begin
Result:= TMathObject.Create;
end; exports
CreateObject; {$R *.res} begin
end.
In the next example a TMathObject object instance is destroyed by assigning nil value to MathObj interface reference.
In most cases there is no need for doing it because an object is
destroyed automatically when all interface references goes out of scope.
In the following code the MathObj interface reference is a global variable and never goes out of scope,
so assigning it to nil explicitly makes sense:
program MathTest;
{$APPTYPE CONSOLE}
uses
BaseMath in 'BaseMath.pas';
function CreateObject: IBaseMath; external 'MathDll.dll';
var
MathObj: IBaseMath;
begin
MathObj:= CreateObject;
MathObj.Operand:= ;
Writeln('Square = ', MathObj.Square::, '; Cube = ', MathObj.Cube::);
MathObj:= nil;
Write('Press ''Enter'' key ... ');
Readln;
end.
The interfaced demo works fine, but when I try to load/unload the DLL dinamically,
an access violation is raised at the end of the program in the System unit at the _IntfClear function.
program MathTest;
{$APPTYPE CONSOLE}
uses
Windows,
BaseMath in ‘BaseMath.pas';
//function CreateObject: IBaseMath; external ‘MathDll.dll';
type
TCreateObject = function: IBaseMath; stdcall;
var
CreateObject: TCreateObject;
MathObj: IBaseMath;
Dll: THandle;
begin
Dll := LoadLibrary(‘MathDll.dll’);
CreateObject := GetProcAddress(Dll, ‘CreateObject’);
MathObj:= CreateObject;
MathObj.Operand:= ;
Writeln(‘Square = ‘, MathObj.Square::, ‘; Cube = ‘, MathObj.Cube::);
MathObj:= nil;
FreeLibrary(Dll);
Write(‘Press ”Enter” key … ‘);
Readln;
// Access Violation is raised here
end.
Sorry. It was a calling convention problem.
TCreateObject = function: IBaseMath;
works fine.
Great articles.
Great Approach of explanation.
I read a lot about interfaces, but this articles make you understand EXACTLY what is an Interface.
The three examples is the best way to progressively move from one concept to the other.
Thank you very much for your efforts.
Why we need interfaces in Delphi的更多相关文章
- Delphi Interfaces
http://www.delphibasics.co.uk/Article.asp?Name=Interface The reason for interfaces Classes that ex ...
- [Delphi] Delphi版本号对照
VER300 Delphi Seattle / C++Builder Seattle 23 230 (Delphi:Win32/Win64/OSX/iOS32/iOS64/An ...
- Delphi的分配及释放---New/Dispose, GetMem/FreeMem及其它函数的区别与相同
转载自:http://www.cnblogs.com/qiusl/p/4028437.html?utm_source=tuicool 我估摸着内存分配+释放是个基础的函数,有些人可能没注意此类函数或细 ...
- Delphi开发的IP地址修改工具
用Delphi进行开发的,直接修改注册表,需重启电脑后才生效
- Delphi XE5教程3:实例程序
内容源自Delphi XE5 UPDATE 2官方帮助<Delphi Reference>,本人水平有限,欢迎各位高人修正相关错误! 也欢迎各位加入到Delphi学习资料汉化中来,有兴趣者 ...
- delphi 保存网页MHT
delphi 保存网页MHT uses ADODB_TLB, CDO_TLB, ComObj,MSHTML;{$R *.dfm}{能把网页如 WWW.QQ.COM保存为一个单文件 .MHT但不能把 ...
- Delphi GDI+ Library
GDI+ LibraryThis library enables GDI+ functionality for Delphi 2009 and later. It differs from other ...
- Delphi实现HTMLWebBrowser实现HTML界面
HTML的界面有以下特点:图文混排,格式灵活,可以包含Flash.声音和视频等,实现图文声像的多媒体界面,而且易于建立和维护.另外,HTML的显示环境一般机器上都具备,通常不需要安装额外的软件.当然, ...
- [转]Delphi I/O Errors
The following are the Windows API (and former DOS) IO errors, which are also the IO errors often ret ...
随机推荐
- linux下动态库编译的依赖问题
这里主要是想试验一下,对一个具有多层嵌套的动态库进行编译时,是否要把最底层的库也包含进来的问题,结论是:只要直接依赖的库名称,不需要最底层库名称. 一,目录结构ZZZ├── add│ ├── ad ...
- [Everyday Mathematics]20150304
证明: $$\bex \frac{2}{\pi}\int_0^\infty \frac{1-\cos 1\cos \lm-\lm \sin 1\sin \lm}{1-\lm^2}\cos \lm x\ ...
- 使用Selenium+appium进行App测试
1. 启动安卓虚拟机.
- 用python3破解wingIDE
值得注意的是,python2的整除/在python3中变成了//,sha方法细化成了sha1和sha256,所以破解文件需要更改加密方式和整除部分的编码方式,经过修改后,这个文件可以完美演算出破解码, ...
- linux 如何让程序在开机时启动,关机前关闭
可以将自己所写的script的文件名写入/etc/rc.d/rc.local(用户自定义开机启动程序) 中,在/etc/rc.d/init.d 中貌似也可以
- python中类的总结
1. 类中的方法 在类里主要有三种方法: a.普通方法:在普通方法定义的时候,需要一个对象的实例参数,从而在类中定义普通方法的时候,都必须传送一个参数self,那么这个参数也就是object b.类方 ...
- Android APP的安装路径
转载自:http://blog.csdn.net/libaineu2004/article/details/25247711 一.安装路径在哪? Android应用安装涉及到如下几个目录: syste ...
- 【Python学习笔记】字符串操作
字符串的表示 python中的字符串是一个常量,可以使用单引号'',双引号""或三引号""" """来创建一个字符串常量 ...
- 新工程软连接到原来的工程的out目录后,可以直接编译模块
P508B_App_old_developer/alps$ ln -s ../../P508B_App/alps/out 连接后,第一次编译后要加分支 ./mk hedy89_we_jb2 mm p ...
- HP ILO2 使用详细教程
iLO是Intergrated Light-Out的缩写,是惠普特有的远程管理功能,目前最新的版本是iLO2.通过iLO2可以实现硬件级别的服务器远程管理,包括开关机.重启.服务器状态的监控.虚拟KV ...