queryinterface(querymapper)
delphi的tinterfacedobject怎么释放
悟透delphi第四章接口
前不久,有位搞软件的朋友给我出了个谜语。谜面是“相亲”,让我猜一软件术语。我大约想了一分钟,猜出谜底是“面向对象”。我觉得挺有趣,灵机一动想了一个谜语回敬他。谜面是“吻”,也让他猜一软件术语。一分钟之后,他风趣地说:“你在面向你美丽的对象时,当然忍不住要和她接口!”。我们同时哈哈大笑起来。谈笑间,似乎我们与自己的程序之间的感情又深了一层。对我们来说,软件就是生活。
第一节接口的概念
“接口”一词的含义太广泛,容易引起误解。我们在这里所说的接口,不是讨论程序模块化设计中的程序接口,更不是计算机硬件设备之间的接口。现在要说的接口,是一种类似于类的程序语言概念,也是实现分布式对象软件的基础技术。
在DELPHI中,接口有着象类一样的定义方式,但不是用保留字class,而是用interface。虽然接口和类有着相似的定义方式,但其概念的内涵却有很大的不同。
我们知道,类是对具有相同属性和行为的对象的抽象描述。类的描述是针对现实世界中的对象的。而接口不描述对象,只描述行为。接口是针对行为方法的描述,而不管他实现这种行为方法的是对象还是别的什么东西。因此,接口和类的出发点是不一样的,是在不同的角度看问题。
可以说,接口是在跨进程或分布式程序设计技术发展中,产生的一种纯技术的概念。类的概念是一种具有普遍性的思想方法,是面向对象思想的核心。但是,接口概念也的确是伴随面向对象的软件思想发展起来的。用接口的概念去理解和构造跨进程或分布式的软件结构,比起早期直接使用的远过程调用(RPC)等低级概念更直观和简单。因为可以象理解一个对象一样理解一个接口,而不再关心这个对象是本地的或远程的。
在DELPHI中,接口被声明为interface。其命名原则是:接口都以字母I开头命名,正如类都以字母T开头一样。在接口的声明中只能定义方法,而不能定义数据成员。因为,接口只是对方法和行为的描述,不存储对象的属性状态。尽管在DELPHI中可以为接口定义属性,但这些属性必须是基于方法来存取的。
所有的接口都是直接或间接地从IUnknown继承的。IUnknown是所有接口类型的原始祖先,有着类概念中TObject的相同地位。“一个接口继承另一个接口”的说法其实是不对的,而因该说“一个接口扩充了另一个接口”。接口的扩充体现的是一种“兼容性”,这种“兼容”是单一的,绝不会存在一个接口同时兼容两个父接口的情况。
由于接口只描述了一组方法和行为,而实现这些方法和行为必须靠类。接口是不能创建实例的,根本就不存在接口实例之说,只有类才能创建对象实例。但一个接口的背后一定会有一个对象实例,这个对象就是接口方法的实现者,而接口是该对象一组方法的引用。
从概念上讲,一个对象的类可以实现一个或多个接口。类对接口的责任只是实现接口,而不应该说类继承了一个或多个接口。“实现”一词和“继承”一词有不同的含义,应该从概念上区分开来。
一般情况下,声明接口时需要一个能唯一标识该接口类型的GUID标识符。接口类型是要被分布在不同进程空间或计算机上的程序使用的,不象类的类型只是在一个程序空间内标识和使用。为了保证一种接口类型在任何地方都能被唯一识别,就必须要一种有效标识不同接口的方法。用人工命名的方法是不行的,没有谁能保证你开发的接口不会与别人重名。于是,一种所谓“全球唯一标识符”GUID(Globally Unique Identifier)应运而生。它是通过一种复杂的算法随机产生的标识符,有16个字节长,可以保证全世界任和地方产生的标识是不同的。在DELPHI的编辑环境中,你可以用Ctrl+Shift+G轻松产生一个GUID标识符,来作为接口的唯一标识。
为接口指定GUID是必要的。虽然,不指定接口的GUID也可以常常可以编译通过,但在使用一些与接口识别和转换相关的功能时一定会有问题。特别是在基于COM的程序开发中,GUID一定不可少。
接口的概念其实很简单,但却在分布式软件开发中起了关键作用。有的朋友之所以认为接口比较复杂,主要是因为不了解接口的概念和原理。因为人们总是对自己未知的东西有一种神秘感。这种神秘感往往会使人对未知世界产生畏惧心理。要揭开接口的神秘面纱,就必须不断的去学习和理解接口的奥秘。其实,在探索的过程中还会有许多的乐趣,你说对吧。
第二节 IUnknown
因为IUnknown是所有接口的共同祖先,所以一定要首先了解它。知道事情的起因,可以有效地帮助我们理解事情的过程和结果。IUnknown的原始定义是在System.pas单元中。因为定义在System.pas单元,那么一定是与系统或编译器相关的原始东西。一看IUnknown的定义,很简单,一共才6行。
IUnknown= interface
['{00000000-0000-0000-C000-000000000046}']
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
不过,这6行定义代码可是接口世界的基础。其中的三个接口方法蕴涵着简单而又博大精深的哲理,理解这些哲理将使我们在编写基于接口的程序时受益非浅。
IUnknown的这三个接口方法是每一个接口对象类必须实现的方法,是接口机制的基础方法。为什么说这三个方法是接口机制的基础?且听我漫漫道来。
首先来谈谈QueryInterface接口方法。我们知道一个对象类是可以实现多个接口的。任何接口对象都一定实现了IUnknown接口。因此,只要你获得了一个接口指针,那一定可以通过这个接口指针调用QueryInterface方法。而调用QueryInterface就可以知道这个接口指针还实现了一些什么接口。这对接口编程机制来说非常重要。判断一个接口指针是否实现了某接口功能,不同接口类型之间的接口匹配和转换,都与QueryInterface方法相关。
QueryInterface有两个参数和一个返回值。第一个参数是接口类型的标识,即一个16字节的GUID标识。由于DELPHI编译器知道每种接口对应什么GUID,所以你可以直接使用象ImyInterface之类的标识符作为第一个参数。如果,该接口支持第一个参数指定的接口类型,则将获得的接口指针通过第二个参数Obj送回给调用程序,同时返回值为S_OK。
从这里也可以看出,为什么为接口指定GUID标识是必要的。因为,QueryInterface方法需要这样的标识,而它又是接口和匹配和转换机制的基础。
接下来我们再谈谈_AddRef和_Release极口方法。_AddRef和_Release接口方法是每一种要接口对象类必须实现的方法。_AddRef是增加对该接口对象的引用计数,而_Release是减少对接口对象的引用。如果接口对象的引用计数为零,则要消灭该接口对象并释放空间。这本是接口机制要求的一个基本原则,就好象1+1=2这样简单的道理,不需要深奥的解释。数学家才会有兴趣去研究一加一为什么会等于二?但数学家对1+1=2的理解是透彻的。同样,对接口对象引用机制的深刻理解,会让我们明白许多道理,这些道理将为我们的开发工作带来益处。
有位大师曾说过:接口是被计数的引用!
要理解这句话,我们首先要理解“引用”的概念。“引用”有“借用”的意思,表明一种参考关系。引用的一方只存在找到被引用一方的联系,而被引用的一方才是真正的中心。由于,通过这种引用关系可以找到对象,因此,引用实际就是该对象的身份代表。在程序设计中,引用实际上是一种指针,是用对象的地址作为对象的身份代表。
在不是基于接口机制的程序中,本不需要就对象的引用关系进行管理。因为,非接口对象的实例都在同一个进程空间中,是可以用程序严格控制对象的建立、使用和释放过程的。可是,在基于接口机制的程序中,对象的建立、使用和释放可能出现在同一进程空间中,也可能出现在不同的进程空间,甚至是Internet上相隔千里的两台计算机中。在一个地方建立一个接口,可能实现这个接口的对象又存在于另一个地方;一个接口在一个地方建立后,又可能会在另一个地方被使用。在这种情况下,要想使用传统的程序来控制对象的建立和释放就显得非常困难。必须要又一种约定的机制来处理对象的建立和释放。因此,这一重任就落到了IUnknown的_AddRef和_Release的身上。
这种接口对象引用机制要求,接口对象的建立和释放由对象实例所在的程序负责,也就是由实现接口的对象类负责。任何地方引用该对象的接口时,必须调用接口的_AddRef方法。不再引用该对象时,也必须调用接口的_Release方法。对象实例一旦发现再也没有被任何地方引用时,就释放自己。
正是为了解决接口对象实例空间管理的问题,_AddRef和_Release方法才成为所有接口对象类必须实现的方法。
第三节接口对象的生*
初看本节的标题似乎有点吓人。接口对象怎么会和生与*联系起来呢?接口对象的生*真的那么重要吗?一个好的统治者应该关心百姓的生*,同样,一个好的程序员也应该关心对象的生*。而接口对象又是流浪在分布式网络中的游子,我们更应该关心他们的生*!
由于,接口对象是伴随接口引用的产生而建立,又伴随接口引用的完结而消亡。在DELPHI中使用接口,似乎没有人关心,实现接口的对象是怎样出身又怎样*亡的。这正是DELPHI中使用接口的简单性,也是其在解决接口机制的使用问题上所追求的目标。需要接口时总有一个对象会为她而生,一旦不再引用任何接口时这个对象又无怨无艾的*去,绝不拖累系统一个字节的资源。真有点“春蚕到*丝方尽,蜡炬成灰泪始干”的凄情。
因为接口对象的生*直接和引用该对象的接口数目有关,所以研究在什么情况下会增加一次接口引用,又在什么情况下会减少一次接口引用,是了解接口对象生*的关键。
现在我们来实现一个最简单的接口对象类TIntfObj,它只实现了IUnknown接口中定义的三个基本方法。有的朋友一看就知道,这个类实际抄袭了DELPHI中TInterfacedObject类的部分代码。只是我们分别在_AddRef和_Release方法中增加了一些信息输出语句,以便于我们探索接口对象的生*问题。请看下面的程序:
program ProgramA;
uses
SysUtils, Dialogs;
type
TIntfObj= class(TObject, IUnknown)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
function TIntfObj.QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
const
E_NOINTERFACE= HResult($80004002);
begin
if GetInterface(IID, Obj) then Result:= 0 else Result:= E_NOINTERFACE;
end;
function TIntfObj._AddRef: Integer; stdcall;
begin
INC(FRefCount);
ShowMessage(Format('Increase reference count to%d.', [FRefCount]));
result:=FRefCount;
end;
function TIntfObj._Release: Integer; stdcall;
begin
DEC(FRefCount);
if FRefCount<> 0 then
ShowMessage(Format('Decrease reference count to%d.', [FRefCount]))
else begin
Destroy;
ShowMessage('Decrease reference count to 0, and destroy the object.');
end;
result:=FRefCount;
end;
var
aObject:TIntfObj;
aInterface:IUnknown;
procedure IntfObjLife;
begin
aObject:=TIntfObj.Create;
aInterface:=aObject;//增加一次引用
aInterface:=nil;//减少一次引用
end;
begin
IntfObjLife;
end.
我们需要用单步调试功能来研究接口引用计数的增减与接口生*的关系。因此,建议你将Options选项中Complier页的Optimization项清除,以避免编译器会优化掉我们需要的指令。
当程序执行到IntfObjLife子程序的三行代码时,请一步一步的调试代码。你会发现,当发生一次对接口类型变量的赋值行为时,就会引发对接口引用计数的增减。
执行语句
aInterface:=aObject;
就会出现“Reference count increase to 1.”的信息,表明增加了一次接口引用。
而执行语句
aInterface:=nil;
就会出现“Reference count decrease to 0, and destroy the object.”,表明接口引用减少到零并且删除了接口对象。
所以,我们可以得出结论:当将引用值赋值给接口类型的变量时,会增加对接口对象的引用计数;而当清除接口类型变量的引用值时(赋nil),就会减少对接口对象的引用计数。
在看看下面的代码,加深一下对这一结论的理解。
var
aObject: TIntfObj;
InterfaceA, InterfaceB: IUnknown;
……
aObject:= TIntfObj.Create;
InterfaceA:= aObject;//引用增加至1
InterfaceA:= InterfaceA;//引用增加至2,但立即又减少至1
InterfaceB:= InterfaceA;//引用增加至2
InterfaceA:= nil;//引用减少至1
InterfaceB:= InterfaceA;//引用减少至0,释放对象
……
无论是将接口对象赋值给变量,还是将接口变量赋值给接口变量,以及将nil赋值给接口变量,都印证这一结论。有趣的是,其中的InterfaceA:= InterfaceA一句执行时,接口对象的引用先增加然后立即减少。为什么会这样呢?留给你自己去思考吧!
接着,我们再来看看下面的代码:
procedure IntfObjLife;
var
aObject:TIntfObj;
aInterface:IUnknown;
begin
aObject:=TIntfObj.Create;
aInterface:=aObject;
end;
这个过程与前面那个过程不同的是,将变量定义为局部变量,并且最后没有给接口变量赋nil值。单步调试这段代码我们发现,在程序运行到子程序end语句之前,接口对象的引用还是减少为0并被释放。这是为什么呢?
我们知道,变量是有作用域的。全局变量的作用域是程序的任何地方,而局部变量的作用域只是在相应的子程序内。一旦变量离开它的作用域,变量本身已经不存在,它存储的值更是失去意义了。所以,当程序即将离开子程序时,局部变量aInterface将不在存在,而其所存储的接口对象引用值也将失去意义。聪明的DELPHI自动减少对接口对象的引用计数,以确保程序在层层调用和返回中都能正确地管理接口对象的内存空间。
因此,我们又可以得出新的结论:当任何接口变量超出其作用域范围的时候,都会减少相关接口对象的引用计数。
要注意的是,子程序的参数变量也是一种变量,它的作用域也是在该子程序范围内。调用一个含有接口类型参数的子程序时,由于参数的传递,相关接口对象的引用计数会被增加,子程序返回时又被减少。
同样,如果子程序的反回值是接口类型时,返回值的作用域是从主调程序的返回点开始,直到主调程序最后的end语句之前的范围。这种情况同样会引起对接口对象引用计数的增减。
该总结一下对象的生与*了。盖棺论定后我们可以得出以下原则:
1.将接口对象的引用值赋值给全局变量、局部变量、参数变量和返回值等元素时,一定会增加接口对象的引用计数。
2.变量原来存储的接口引用值被更改之前,将减少其关联对象的引用计数。将nil赋值给变量是一个赋值和修改接口引用的特例,它只减少原来接口对象引用计数,不涉及新接口引用。
3.存储接口引用值的全局变量、局部变量、参数变量和返回值等元素,超出其作用域范围时,将自动减少接口对象的引用计数。
4.接口对象的引用计数为零时,自动释放接口对象的内存空间。(在一些采用了对象缓存技术的中间件系统中,如MTS,可能并不遵循这一原则)
需要提醒你的是,一旦你将建立的接口对象交给接口,对象的生*就托付给接口了。就好象将宝贝女儿嫁给忠厚的男人一样,你应该完全信任他,相信他能照顾好她。从此,与对象的联系都要通过接口,而不要直接与对象打交道。要知道,绕过女婿直接干预女儿的事情有可能会出大问题的。不信,我们看看下面的代码:
program HusbandOfWife;
type
IHusband= interface
function GetSomething:string;
end;
TWife= class(TInterfacedObject, IHusband)
private
FSomething:string;
public
constructor Create(Something:string);
function GetSomething:string;
end;
constructor TWife.Create(Something:string);
begin
inherited Create;
FSomething:=Something;
end;
function TWife.GetSomething:string;
begin
result:= FSomething;
end;
procedure HusbandDoing(aHusband:IHusband);
begin
end;
var
TheWife: TWife;
TheHusband: IHusband;
begin
TheWife:= TWife.Create('万贯家财');
TheHusband:= TheWife;//对象TheWife委托给一般接口变量TheHusband
TheHusband:= nil;//清除接口引用,对象消失
TheWife.GetSomething;//直接访问对象,一定出错!
TheWife:= TWife.Create('万贯家财');
HusbandDoing(TheWife);//对象委托给参数接口变量aHusband,返回时对象消失
TheWife.GetSomething;//直接访问对象,一定出错!
end.
pageoffice j*a 怎么用
OpenOffice j*a api:
简单的说就是利用j*a程序可以*作OpenOffice的所有功能,比如创建doc文档,*文字,设置文字格式等等。
1. OpenOffice给程序员提供了一个叫UNO(UniversalNetwork Objects)的组件技术.我理解的UNO: OpenOffice类似于web程序中的服务器,程序员写的代码类似于客户端,利用UNO提供的接口和服务去完成对OpenOffice文档的*作。所以写程序首先要搭建 UNO环境:
1.下载 OpenOffice
2.复制UNO提供的jar包: unoil.jar, j*a_uno.jar, juh.jar, jurt.jar, ridl.jar, unoloader.jar.(ps:安装了SDK之后在文件夹找)到自己的工程中,引入它们。
3.下载文档:DevelopersGuide.pdf.
4.安装了SDK后,重新启动一下机器,然后就可以按照 DevelopersGuide来学习 UNO编程了。
5.需要*a环境。
补充:安装了SDK后, j*a, c++帮助文档,样例程序,其他关于sdk的信息都放在本地openOffice安装路径一个叫sdk目录下面,enjoy it!
总结一下已经实现的功能和碰到的问题汇总:
1.首先要得到远程office组件的上下文.通过:
com.sun.star.uno.XComponentContext xContext= com.sun.star*p.helper.Bootstrap.bootstrap();
得到,如果OpenOffice安装路径不是在工程的路径下面(我自己猜的),就会报:
com.sun.star*p.helper.BootstrapException: no office executable found!
解决办法:黑其源代码,看了源代码就会发现其实OpenOffice是在寻找本地的soffice的shell文件,所以弄个变量来保存soffice在系统中的路径,重新写一个Bootstrap就可以了。详细请参照:论坛。
2.得到 XMultiComponentFactory(ComponentFactory工厂)
com.sun.star.lang.XMultiComponentFactory xMCF= xContext.getServiceManager();
3.得到各种组件可以通过下面代码:
// docType是与 soffice同目录下面的OpenOffice的其他shell文件,swrite等等
protected XComponent newDocComponent(String docType)
throws j*a.lang.Exception{
String loadUrl=”private:factory/”+ docType;
mxRemoteServiceManager= this.getRemoteServiceManager();
Object desktop= mxRemoteServiceManager.createInstanceWithContext(
“com.sun.star.frame.Desktop”, mxRemoteContext);
XComponentLoader xComponentLoader=(XComponentLoader) UnoRuntime
.queryInterface(XComponentLoader.class, desktop);
PropertyValue[] loadProps= new PropertyValue[0];
return xComponentLoader.loadComponentFromURL(loadUrl,”_blank”, 0,
loadProps);
}
4.得到 XTextDocument
XComponent xEmptyWriterComponent= newDocComponent(“swriter”);
XTextDocument mxDoc=(XTextDocument) UnoRuntime.queryInterface(XTextDocument.class,
xEmptyWriterComponent);
5.得到一个文档的引用
XText mxDocText= mxDoc.getText();
6.得到文档的属性列表
XPropertySet mxDocProps=(XPropertySet) UnoRuntime.queryInterface(
XPropertySet.class, mxDoc);
7.建立光标,用来*新的内容。
XTextCursor mxDocCursor= mxDocText.createTextCursor();
XSentenceCursor xSentenceCursor=(XSentenceCursor) UnoRuntime
.queryInterface(XSentenceCursor.class, mxDocCursor);
XWordCursor xWordCursor=(XWordCursor) UnoRuntime.queryInterface(
XWordCursor.class, mxDocCursor);
8.得到光标属性列表
XPropertySet xCursorProps=(XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, mxDocCursor);
9.设置*文字格式
xCursorProps.setPropertyValue(“CharFontName”,”宋体”);
xCursorProps.setPropertyValue(“CharWeight”, new Float(FontWeight.BOLD));
xCursorProps.setPropertyValue(“CharHeight”, new Float(10.5));
//居中显示
xCursorProps.setPropertyValue(“ParaAdjust”, com.sun.star.style.ParagraphAdjust.CENTER);
10.在该光标处*信息
mxDocText.insertString(xSentenceCursor,“Hello World”, true);
11.保存的关键代码
protected void storeDocComponent(XComponent xDoc, String storeUrl)
throws j*a.lang.Exception{
XStorable xStorable=(XStorable) UnoRuntime.queryInterface(
XStorable.class, xDoc);
PropertyValue[] storeProps= new PropertyValue[1];
storeProps[0]= new PropertyValue();
storeProps[0].Name=”FilterName”;
storeProps[0].Value=”MS Word 97″;
openOfficeJ*aLogger.debug(“… store\””+ xDoc.toString()+”\” to\””+ storeUrl
+”\”.”);
xStorable.storeAsURL(storeUrl, storeProps);
}
本文链接:http://www.yangwm.com/html/87965042.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件举报,一经查实,本站将立刻删除。