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

来源:计算机等级考试    发布时间:2012-08-28    计算机等级考试视频    评论

  这个方法根据传入的一个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;

  function _AddRef: Integer; stdcall;

  function _Release: Integer; stdcall;

  end;

  function TMyInterfacedObject._AddRef: Integer;

  begin

  result:=1; //我们已经不需要引用计数了

  end;

  function TMyInterfacedObject._Release: Integer;

  begin

  result:=1;

  end;

  function TMyInterfacedObject.QueryInterface(const IID: TGUID;

  out Obj): HResult;

  begin

  if GetInterface(IID, Obj) then

  Result := 0

  else

  Result := E_NOINTERFACE;

  End;

  很高兴的告诉大家,到此我们已经解决了对象生命周期管理中的所有问题,我们所管理的对象可以正常工作了!而且我们还可以去掉上面的技巧性代码,从这个TmyInterfacedObject继承就已经足以解决所有的问题了。也许有人可以看到上面的代码已经极大的破坏了COM规范,然而这正是本文的目的(J),通过我们的改进,我们手工管理的对象工作的很好,而且这也正是我们所需要的不是吗?

上一页123下一页

视频学习

我考网版权与免责声明

① 凡本网注明稿件来源为"原创"的所有文字、图片和音视频稿件,版权均属本网所有。任何媒体、网站或个人转载、链接转贴或以其他方式复制发表时必须注明"稿件来源:我考网",违者本网将依法追究责任;

② 本网部分稿件来源于网络,任何单位或个人认为我考网发布的内容可能涉嫌侵犯其合法权益,应该及时向我考网书面反馈,并提供身份证明、权属证明及详细侵权情况证明,我考网在收到上述法律文件后,将会尽快移除被控侵权内容。

最近更新

社区交流

考试问答