谈组件技术二 必备知识Word文档格式.docx
《谈组件技术二 必备知识Word文档格式.docx》由会员分享,可在线阅读,更多相关《谈组件技术二 必备知识Word文档格式.docx(11页珍藏版)》请在冰豆网上搜索。
procedureButton1Click(Sender:
TObject);
procedureFormCreate(Sender:
private
{Privatedeclarations}
public
{Publicdeclarations}
end;
ILC=Interface(IUnknown)
['
{4FFE6DDB-80B9-4E2D-A05F-5F3B35311ED7}'
]
//GUID,它是用来唯一标识一个接口的标识符,可以通过Ctrl+Shift+G产生一组GUID,而且你可以认为你所产生的GUID是全世界唯一的。
永远不要担心GUID会被用完。
procedureSetValue(NewValue:
String);
functionGetValue:
String;
TLC=Class(TInterfacedObject,ILC)
Value:
destructorDestroy;
override;
var
Form1:
TForm1;
IMyLC:
ILC;
implementation
{$R*.dfm}
{TLC}
destructorTLC.Destroy;
begin
Application.MessageBox('
资源已经被完全释放'
'
操作提示'
MB_OK+MB_ICONINFORMATION);
inherited;
functionTLC.GetValue:
String;
Result:
=Value;
ShowMessage(Result);
procedureTLC.SetValue(NewValue:
String);
Value:
=NewValue;
procedureTForm1.Button1Click(Sender:
IMyLC.SetValue('
第一个COM例程'
);
IMyLC.GetValue;
procedureTForm1.FormCreate(Sender:
=TLC.Create;
end.
Interfaces接口
接口定义了包含一组抽象方法的类型。
为什么说是包含了一组抽象的方法类型呢?
原因是接口的继承可以完全进行类如类中的Overload(当然,没有这样的语法,但是效果是完全一样的),这也正是接口的特殊所在,同时,接口里的方法是完全公用的,就如Public,但没有必要加这个关键字。
一个类,即使是自一个简单的基类继承而来也可以实现任意多的接口。
接口与抽象类有些相似(即没有任何字段并且所有方法都是抽象方法的类),并且Delphi提供了附加的功能。
Delphi的接口有时很象COM(组件对象模型)接口,然而,可以利用delphi为我们提供的Interface实现组件对象模型的接口,但是使用Delphi的接口并不需要你了解有关COM的内容,同时你还可以将接口用作其他许多用途。
声明一个新的接口——它继承于一个已经存在的接口。
默认的继承于IunKown接口,接口的声明包含了方法和属性的声明,但是没有字段。
正如所有的类都继承于TObject一样,所有的接口类继承自IUnknown。
接口IUnknown定义了三个方法:
_AddRef,_Release,以及QueryInterface。
如果你对COM熟悉的话,对此三个方法便不会陌生。
前两个方法用于管理实现此接口的对象的生命周期引用计数。
第三个方法用于存取对象可能实现的其他接口。
至于引用计数则是一个比较活的引用,在OOP里,很多地方都引用了引用计数技术。
你将会看到,通过引用计数功能,来对组件进行一些违背组件初衷的操作,比如提前释放组件、推迟释放组件,甚至不释放组件,当然,我不推荐这种作法,除非你这样做有自己充分的理由,在后边的例程中,你将会看到这样的做法。
声明了接口并不等于就可以应用这个接口,必须要有一个协调对象类(CoClass)来实现这个类,如果你对TypeLibrary比较熟悉的话,应该对这个实现类很清楚,这是后话,您将会在ocx实例中看到它的用法。
在此强调一点:
没有实现的接口是没有任何意义的,之所以这样说是因为某一个接口最终目的就是为用户服务,被用户所用,但是只有对这个接口进行了实现才可以对其应用(前边我提起过,接口类如一个抽象类的原因也是如此的,而且强调接口没有字段是因为它不具备存储的能力!
);
提醒各位一点,delphi不支持多继承,但是可以利用接口来实现多继承,还有另外一种可以通过设计模式来实现多继承(不属于我们讨论的范畴),当你想要声明一个实现了一个或者多个接口的类时,你必须实现接口中所声明的所有方法。
新的类可以直接实现接口的方法,也可以将此实现委托给一个属性——其值为一个接口。
实现_AddRef,_Release以及QueryInterface方法最简单的方法就是继承TInterfacedObject及其派生类的方法,当然你也可以继承自其他类如果你想自己定以方法的实现的话。
如:
TvClass=Class(TinterfacedObject,IvInterface);
理解:
TvClass作为IvInterface接口的一个实现类,它是继承于TinterfacedObject而实现于IvInterface的,同时,它了可以实现多个接口。
新类在实现接口的方法时必须使用于接口方法一致的方法名,参数以及调用约定。
Delphi自动将类的方法与接口的相应方法配对。
如例程:
MyObject:
Tobject;
MyNumber:
IformattedNumber;
Begin
=TformattedInteger.Create(12);
…
ifMyObject.GetInterface(IformattedNumber,MyNumber)then
ShowMessage(MyNumber.FormattedString);
End;
{说明:
此处的实例摘自我的一个例程,详细可参考例程,IformattedNumber是一个接口,TformattedNumber是它的CoClass}
假如要使用不同的方法名,你可以使用不同的方法名来重定向接口的方法。
用作重定向的方法必须具有于接口的方法一致的参数和调用约定。
这一特性非常重要,当一个类需要实现多个接口,而其中有重复的方法名时尤其如此。
在这种情况下就需要将某一个接口的方法利用别名或是利用Implements指示符将接口的实现委托给一个属性。
该属性的值必须得是该类将要实现的接口类型。
当对象被映射到该接口上时,Delphi自动获取该属性的值,并且返回该接口。
在此将不对这些细节问题进行阐述。
Referencecounting引用计数
对象有一个创建、释放的过程,接口也同样有,组件更是如此,接口也有生命周期,接口的生命周期是随着一个计数变量(FCount)而决定的。
而何时释放一个组件是属于引用技术范畴,(期望以后的文章我会对此进行阐述)某一个组件的释放有四种方式,它可以随着其接口、自身、以及组件对象的决定而在不同的时间进行释放,以及强制释放IinterFace:
=Nil在此,你现在只要明白了接口、组件的释放和引用计数变量有很大的关系。
编译器触发对_AddRef和_Release的调用以管理接口对象的生命周期。
要使用Delphi的自动的引用计数,声明一个接口类型的变量即可。
当你将一个接口引用赋值给一个接口变量时,Delphi自动调用_AddRef。
当改变量离开作用域时,Delphi自动调用_Release。
_AddRef和_Release的行为完全取决于你。
如果你从TInterfacedObject继承,则这些方法完成引用计数的功能。
_AddRef方法用于增加引用计数,_Release用于将引用计数减一。
当引用计数为0时,_Release方法将释放对象。
如果你从其他类继承而来,则你可以定义自己的方法。
但是,你应当正确的实现QueryInterface方法,因为Delphi正是基于此来实现As操作。
对于引用计数技术,我将会在另外的文章中加以阐述,并且就不同的情况下,_AddRefand_Release操作以及接口转化等做详细的说明,如果你有兴趣请关注我近期内写的一些文章。
在下边将实现一些简单的转代以供参考。
{
说明:
TinterfacedObject实现了IunKown的_AddRef和Release以及QueryInterface,所以我们所声明的CoClass只要继承自它就可以了,否则可能需要你自己写这三个方法的实现方法,然而,这并非是决对的。
只是TinterFacedObject提出的比较早,所以,很多资料都是以它作为基类。
在下边的实例中,你将可以看到如何手动的实现这三个方法,而且破坏引用计数的规则。
}
Delphi调用QueryInterface来对接口实现部分as操作的功能。
你可以使用as操作符将一个接口转换为另外一个接口。
Delphi调用QueryInterface以获得一个新的接口引用。
如果QueryInterface返回一个错误,则as操作将触发一个运行期错误。
(在SysUtils单元中该运行其错误被映射到EIntfCastError异常类中。
)
你可以用自己的方式来实现QueryInterface方法,虽然可能你更倾向于与TInterfacedObject的实现接近的那种。
如下显示的是一个类实现了普通的QueryInterface方法,但是对于_AddRef和_Release方法的实现确大不相同。
稍后你将看到这样做有什么用处
无需引用计数的接口类
typeTNoRefCount=class(TObject,IUnknown)protectedfunctionQueryInterface(constIID:
TGUID;
outObj):
HResult;
stdcall;
function_AddRef:
Integer;
function_Release:
end;
functionTNoRefCount.QueryInterface(constIID:
HResult;
beginifGetInterface(IID,Obj)thenResult:
=0elseResult:
=Windows.E_NoInterface;
functionTNoRefCount._AddRef:
beginResult:
=-1end;
在此我之所以要花这么多的篇幅来说明接口就是应为接口是组件中的一切,脱离了接口的组件将不成组件,然而这里所介绍的接口却又是少的可怜,我会在以后的文章中进行详细的说明。
各位现在对接口是否有一个初步的了解了呢?
如果你感觉有很多疑问而且又迫切的想知道答案,可以联系我,或是到上看我以前的有关于接口的文章以及收录在超级猛料中的丁点介绍。
之后,我们将进行一些组件的制作,请关注下一篇文章.
本文来自CSDN博客,转载请标明
出处:
==================================================
谈组件技术
————组件技术的本质COM理论知识
前记:
本来我已经将组件的实际操作ocx写完了,很长时间了,总是不敢放上来,怕大家认为太小儿科,而且,有的朋友也提醒我,写的东西太过于累赘,不是很简明易懂就能揭示核心问题,而ocx也有此方面的嫌疑,所以没有放上来。
好了,继续我们今天的讨论,希望大家看后都有自己的见解。
也许您对这个标题有些疑问,组件技术的本质是COM吗?
申明一点,此处所说的组件技术都是就windows平台而言的,那么在windows平台下,组件的本质是什么?
无可非议,COM!
虽然,DCOM、MTS、COM+、甚至NotNet都已经常的被挂在了程序员的嘴边,可是本质是什么?
就是COM。
暂且不谈我为何要这样说,或许在下边的阐述中,您就会对这个问题自然而然的知道答案。
在介绍COM(ComponentObjectModel,组件对象模型)的时候,我们会省略一些细节,因为我要可以将COM的很多细节都写了出来的话,我想我们其码要谈很长时间,再者,作者在此也不敢说自己已经可以完全的驾驭COM。
但会尽力的在本篇文章和以后的文章中对COM作一个多方位的介绍,力争看过文章的朋友都可以对COM有一个明确的认识,并且可以独立的完成COM组件的编写。
让我们继续。
如上图所示,它就是一个完整的COM组件图,其实可以扩展到任何一个组件中,它们的关系,也如上图所示,包含于被包含的关系。
其中,CoClass是正真的封装接口的部分,我们根据上图所示进行一步一步的分析,实际上,每一个Coclass都可以是一个COM对象,而每个CoClass又可以实现多个Interface,Interface在之前的文章中已经给以了介绍,那么我们此处标识的COM接口和Interface有什么区别呢?
在一定的程度上而言,我们所标识的COM接口和Interface可以认为是一个概念,只是此处为了更能明确的划分出它们的每一个细节才这样做的,希望看到这篇文章的朋友不要混淆。
一位朋友说的相当好,我们所谓的OOP就是源码级上将用户的操作上进行了一定的规范,而组件是从底层,二进制上对用户的操作进行了一定的规范,所以组件才能有抹杀语言的区别!
但无论是作为一个小型的COM组件还是一个大型的COM组件,它都要遵守COM规范来编写,COM组件是以Win32动态连接库(DLL)或是以可拨行文件(Exe)的形式而存在,每一个COM组件都是一些二进制可执行文件。
作为一个组件,必须要作到以下的几点:
它必须以给其它的客户端提供服务的形式而存在,当然,它也可以获取其它的组件的服务。
COM组件可以动态的插入或卸出应用
COM组件必须是动态链接的
COM组件必须隐藏(封装)其内部实现细节
COM组件必须将其实现的语言隐藏
COM组件必须以二进制的形式发布
COM组件必须可以在不妨碍已有用户的情况下被升级
COM组件可以透明的在网络上被重新分配位置
COM组件按照一种标准的方式来宣布它们的存在
……
它既然是以提供服务的形式而存在,并且是完全可以脱离物理机的限制,那么它是如何被各个客户端所认识的呢?
与接口类似,每个COM对象也有一个128位的GUID来标识,称为CLSID(ClassIdentifier,类标识符或类ID),并且,它也是全球唯一的,可以结合接口GUID进行理解。
根据COM组件识意图,我们可以看出一个COM组件可以包含多个COM对象,而这些COM对象是如何联系的呢?
我们是否可以通过对象A而去访问对象B呢?
从理论上而言,是不应该的,甚至一个COM组件只包含一个COM对象,COM对象之间是互不相关的,但是在实际的操作中可以吗?
当然可以,你会在后边看到相关的实例的。
每一个COM对象作为一个黑盒子,它的内部都有什么呢?
就是对接口的实现!
通过实现接口来封装逻辑规则,这也是COM的本质!
所以,在COM中,接口就是一切。
我们可以说脱离接口的COM将不会存在,而没有实现接口的COM是没有任何意义的,对于我们来说,COM组件、对象就是一组接口的集合,只可以通过接口和COM打交道,没有任何接口访问权限的用户,其COM组件对它是没有丝毫的用处的。
这就是封装的体现。
而在上两篇文章中我们介绍了接口,此处将不花费过多的笔墨进行阐述。
OK,在你对COM组件有了这些认识之后,我们现在就可以进行COM组件的进一步分步的讨论,从其最细节的地方来进行讨论。
(对于COM其它的一些知识,如:
IMarshal,代理、存根DLL等相关知识会在以后的文章进行专门的介绍)
COM对象(COMObject)
COM对象?
如何理解COM对象?
他有什么东西?
(在此处,仅以ObjectPascal对本篇文章进行阐述)。
COM对象是接口的集合没错,但是COM对象是如何实现、驾驭这些接口的呢?
其实,我们可以在ComObj单元中看到COM的很多相关类,此处我们将以TComObject为例,以下代码摘自Delphi6
TComObject=class(TObject,IUnknown,ISupportErrorInfo)
private
FController:
Pointer;
FFactory:
TComObjectFactory;
FNonCountedObject:
Boolean;
FRefCount:
FServerExceptionHandler:
IServerExceptionHandler;
functionGetController:
IUnknown;
protected
{IUnknown}
functionIUnknown.QueryInterface=ObjQueryInterface;
functionIUnknown._AddRef=ObjAddRef;
functionIUnknown._Release=ObjRelease;
{IUnknownmethodsforotherinterfaces}
functionQueryInterface(constIID:
TGUID;
{ISupportErrorInfo}
functionInterfaceSupportsErrorInfo(constiid:
TIID):
public
constructorCreate;
constructorCreateAggregated(constController:
IUnknown);
constructorCreateFromFactory(Factory:
constController:
destructorDestroy;
override;
procedureInitialize;
virtual;
functionObjAddRef:
functionObjQueryInterface(constIID:
functionObjRelease:
{$IFDEFMSWINDOWS}
functionSafeCallException(ExceptObject:
TObject;
ExceptAddr:
Pointer):
{$ENDIF}
propertyController:
IUnknownreadGetController;
propertyFactory:
TComObjectFactoryreadFFactory;
propertyRefCount:
IntegerreadFRefCount;
propertyServerExceptionHandler:
IServerExceptionHandler
readFServerExceptionHandlerwriteFServerExceptionHandler;
TComObject是直接从TObject中继承而来的,而且实现了默认接口IUnKnown和ISupportErrorInfo,不同于我们之间说的TinterfaceObjected的是,它实现了COM对象的必要的功能,如functionIUnknown._AddRef=ObjAddRef;
是从_AddRef的引用。
HResult是一个特殊的返回值,它是ISupportErrorInfo的产物,用来标识函数调用的世功或是失败。
如下:
ISupportErrorInfo=interface(IUnknown)
['
{DF0B3D60-548F-101B-8E65-08002B2BD119}'
其实现过程:
functionTComObject.InterfaceSupportsErrorInfo(constiid:
ifGetInterfaceEntry(iid)<
>
nilthen
Result:
=S_OKelse
=S_FALSE;
此处对于另外一个用来处理检测调用失败的处理方法的过程:
OleCheck进行说明,见其代码如下:
procedureOl