手机站
网通分站
电信主站
密 码:
用户名:
当前位置 : 主页>程序设计>delphi>列表

非COM环境下的接口编程-问题,技巧,应用(一)

来源:互联网 作者:west263.com 时间:2008-02-23
西部数码-全国虚拟主机10强!40余项虚拟主机管理功能,全国领先!双线多线虚拟主机南北访问畅通无阻!免费赠送企业邮局,.CN域名,自助建站480元起,免费试用7天,满意再付款! P4主机租用799元/月.月付免压金!

问题和解决技巧:

※引用计数问题(当我们需要手工管理对象的生命周期时):

既然我们在工厂类中定义了私有字段以存贮dll中的诸多对象实例,那从这里得到的一个很显然的观点是我们需要手工管理其中对象的生命周期,我们也许还需要在工厂类中添加一个用于释放所管理对象的方法:

procedure TFooManager.DelAFoo(id:integer);

var

i:integer;

begin

if FooNum>0 then

begin

FList[id].Free;

for i:=id to FooNum-2 do

begin

FList[i]:=FList[i 1]; //移动剩余元素,使对象在flist中保持连续存贮。

end;

FList[FooNum-1]:=nil;

Dec(FooNum);

end;

end;

这个方法根据传入的一个ID值释放指定的对象(在管理多种不同的对象时,还需要接收一个代表所需要释放对象类型的参数以决定释放哪种类型的对象)。好,一切看上去都很正常,但当我们从DLL外在调用工厂方法创建对象的时候问题出现了:

procedure CallCreateFoo;

var

tempfoo:IFoo;//要创建的Foo对象的接口IFoo

begin

tempfoo:=FooMan.CreateAFoo; //FooMan是工厂类的对象的接口

end;

当我们需要在这个过程外使用刚才创建的对象时(例如我们可以使用工厂类的一个方法TFooManager.GetFooByID(id: integer)根据传入的id找到flist中指定的对象,如刚才我们通过工厂方法创建的那对象),会出现一个内存访问错误,仔细观察后会发现我们刚才创建的对象根本不在内存中!为什么?由于delphi中的接口都继承自Iinterface,这是一个与Iunknown一样的接口,这意味着我们的对象都必须实现Iunknown中的那3个方法,更进一步的我们为了不用手工书写这些代码我们的类都继承自默认实现了Iinterface的类TinterfacedObject。然而错误的根源就在这个地方,继承自TinterfacedObject的对象根本不允许我们手工管理它的生命周期,因为Iunknown的实现类会根据对象中的引用计数来维护对象的生命周期,而上面的tempfoo是一个过程的局部变量,当它离开作用域时会被delphi编译器自动调用tempfoo._ Release(这是Iunknown的方法),这个方法在引用计数为0的时候将自动释放对象!而我们在调用TFooManager.CreateAFoo方法时其中仅做了一次as操作result:=FList[FooNum-1] as IFoo;(delphi会在这时自动增加一个对象的引用计数),所以引用计数为1在_ Release后变为0,于是在我们想访问我们创建的对象之前这个对象就已经不存在了。好了,问题的起因弄的很清楚了,要解决也不难,我们只用在返回请求接口之前进行一次_AddRef操作增加引用计数值,这样除非我们手工释放对象,否则引用计数都不会为0,如下的改进:

function TFooManager.CreateAFoo: IFoo;

begin

FList[FooNum-1]:=TFoo.Create;

(FList[FooNum-1] as IUnKnown)._AddRef;

result:=FList[FooNum-1] as IFoo;

end;

好了,似乎我们已经克服了所有的困难,是这样吗?不是,麻烦马上又出现了!当我们这个时候调用工厂类的DelAFoo方法时会抛出更多的异常!。当我们释放对象的时候会调用到TinterfaceObject的free方法,在调用这个方法前编译器会自动调用TInterfacedObject.BeforeDestruction方法(事实上这是一个从Tobject继承下来的方法,但在Tobject中它没有任何的实现),这个方法代码如下:

procedure TInterfacedObject.BeforeDestruction;

begin

if RefCount <> 0 then

Error(reInvalidPtr);

end;

看到这里问题明白了吧,由于我们手动增加了引用计数值,使那个值在释放对象前也不会为0,而上面的代码在引用计数值为0的时候会抛出一个异常。解决这个问题的办法很简单,我们只需要再自己定义一个我们的TinterfacedObject类TourInterfacedObject,在这个类中复写(override,因为这是一个虚函数)BeforeDestruction,让它的代码部分空白:

procedure TInterfacedObject.BeforeDestruction;

begin

end;

然后我们DLL中所有的类只用从TourInterfacedObject继承就可以了。

※改进和手工管理

这次我们再进行测试时就没有任何的问题了吗?如果只是上面的代码的确没有问题了,但问题是我们可能需要在任何地方使用接口操作我们所管理的对象,而delphi编译器会在接口变量离开作用域或者被手工设置为nil时自动调用_IntfClear以决定是否释放实现接口的对象,如果在这之前我们已经调用诸如DelAFoo这样的方法手工释放了我们的对象,那么在调用_IntfClear时,一个访问异常出现了,因为这时对象已经根本不存在了!!现在是应该彻底把对象生命周期交给我们管理而不是交给接口管理的时候了(上面的做法是片面的,因为事实上对象内部仍然存在着一个引用计数值,我们只是用了一点技巧混淆了它对对象生命周期的管理),看来我们又要回到最开始了,我们不得不考虑不要TinterfacedObject而是自己写一个实现Iinterface的类,彻底的抛弃引用计数,并省略到诸如BeforeDestruction之类的不必要的方法:

TMyInterfacedObject = class(TObject, IInterface)

protected

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

文章整理:西部数码--专业提供域名注册虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!