首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何释放接口对象(Delphi 7)

如何释放接口对象(Delphi 7)
EN

Stack Overflow用户
提问于 2011-01-28 03:55:57
回答 4查看 18.6K关注 0票数 16

在我的应用程序的某些部分,我会遇到这样的情况:我收到一个接口,我知道它是一个对象,尽管我不知道确切的类。我必须将该对象存储在一个接口类型的变量中。

最终,我可能会收到该类型的另一个实例,第一个实例必须被丢弃并替换为新的实例。为此,我需要释放接口对象使用的内存(我的接口提供了一个AsObject方法,所以我可以在它上面使用TObject方法)。我的问题是,当我想再次将"nil“赋值给这个变量时,我得到了一个访问冲突。

我写了一个小程序来重现我的情况。我把它贴在这里是为了澄清情况。

代码语言:javascript
复制
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes;

type
   ISomeInterface = interface
      function SomeFunction : String;
      function AsObject : TObject;
   end;

   TSomeClass = class(TComponent, ISomeInterface)
   public
      called : Integer;
      function SomeFunction : String;
      function AsObject : TObject;
   end;

var
   SomeInterface : ISomeInterface;
   i : Integer;

function TSomeClass.SomeFunction : String;
begin
   Result := 'SomeFunction called!';
end;

function TSomeClass.AsObject : TObject;
begin
   Result := Self;
end;

begin
   try
      SomeInterface := nil;

      for i := 1 to 10 do
      begin

         if SomeInterface <> nil then
         begin
            SomeInterface.AsObject.Free;
            SomeInterface := nil;          // <-- Access Violation occurs here
         end;

         SomeInterface := TSomeClass.Create(nil);
         SomeInterface.SomeFunction;       // <-- if commented, Access 
                                           //     Violation does not occur

      end;

   except on e : Exception do
      WriteLn(e.Message);
   end;

end.

所以问题是:我怎样才能正确地释放那个对象?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2011-01-28 05:26:10

假设您有合法的理由这样做(并且使用TComponent,您很有可能这样做-原因请参阅答案末尾),那么问题的发生是因为您在销毁接口变量当前引用的对象后更改了该变量的引用。

对接口引用的任何更改都会生成如下代码:

代码语言:javascript
复制
  intfA := intfB;

变成(简而言之):

代码语言:javascript
复制
  if Assigned(intfA) then
    intfA.Release;

  intfA := intfB;

  if Assigned(intfA) then
    intfA.AddRef;

如果你把它和你的代码联系起来,你应该会看到问题:

代码语言:javascript
复制
  SomeInterface.AsObject.Free;
  SomeInterface := nil;  

变成:

代码语言:javascript
复制
SomeInterface.AsObject.Free;

if Assigned(SomeInterface) then
  SomeInterface.Release;

SomeInterface := nil;  

if Assigned(SomeInterface) then
  SomeInterface.AddRef;

因此,您可以看到,正是由于将NIL赋值给接口而生成的Release()调用导致了访问冲突。

您还应该很快看到有一种简单的方法来避免这种情况,只需将对象的释放推迟到您将接口引用设为空之后:

代码语言:javascript
复制
obj := SomeInterface.AsObject;
SomeInterface := NIL;
obj.Free;

这里的关键问题是,为什么要显式地释放一个接口的对象(并假设引用计数)。

当您更改代码以缓存对象引用并在显式释放对象之前将接口设为空时,您可能会发现obj.Free将导致访问冲突,因为接口引用的设为空本身可能会导致对象被释放。

确保显式释放接口对象是安全的唯一方法是:

1)接口对象覆盖/重新实现了IUnknown,并消除了引用计数生命周期管理

2)在代码中的其他地方没有对该对象的其他接口引用。

如果第一个条件对你没有多大意义,那么这可能是一个好兆头,表明你不应该显式释放对象,因为它几乎肯定是由引用计数管理的。

话虽如此,因为您使用的是一个接口TComponent类,所以只要您的TComponent类没有封装COM对象,那么TComponent就满足条件#1,因此剩下的就是确保其余代码满足条件#2。

票数 31
EN

Stack Overflow用户

发布于 2011-01-28 05:30:46

您不应该使用TComponent作为接口对象的基类,而应该使用TInterfacedObject。TInerfacedObject已经实现了必要的函数来处理Delphi语言中接口的生命周期管理。你也不应该把访问你的接口作为接口和对象。以下是对代码的修改,它工作得很好,没有内存泄漏。

代码语言:javascript
复制
program Project2;
{$APPTYPE CONSOLE}

uses
    SysUtils, Classes;

type
    ISomeInterface = interface
        function SomeFunction: string;
    end;

    TSomeClass = class(TInterfacedObject, ISomeInterface)
    public
        called: Integer;
        function SomeFunction: string;
    end;

var
    SomeInterface: ISomeInterface;
    i: Integer;

function TSomeClass.SomeFunction: string;
begin
    Result := 'SomeFunction called!';
end;

begin
    try
        SomeInterface := nil;
        for i := 1 to 10 do
        begin
            if SomeInterface <> nil then
            begin
                SomeInterface := nil;
            end;
            SomeInterface := TSomeClass.Create;
            SomeInterface.SomeFunction;
        end;
    except
        on e: Exception do
            WriteLn(e.message);
    end;
end.
票数 6
EN

Stack Overflow用户

发布于 2011-01-28 04:51:31

当你有一个接口变量时,比如你的ISomeInterface变量,你不需要释放它,因为它是引用计数的,当它退出作用域时会释放它自己。

阅读罗布·肯尼迪对这个问题的回答:Delphi7, passing object's interface - causes Invalid Pointer Operation when freeing the object

来自http://delphi.about.com/od/beginners/l/aa113004a.htm

一旦接口超出范围,Delphi实际上会自动为您释放接口!在过程或函数中声明的接口在过程结束时自然会超出作用域。当对象被释放或程序结束时,在类中声明的接口或全局声明的接口自然会超出范围。

如果有疑问,请尝试使用FastMM内存管理器并调优内存泄漏检测,以查看对象是否泄漏。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4821158

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档