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

Pab*_*ino 14 delphi memory-management interface object delphi-7

在我的应用程序的某些部分,我遇到的情况是我收到一个我知道是对象的接口,尽管我不知道确切的类.我必须将该对象存储在interface-type变量中.

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

我写了一个小程序来重现我的情况.我在这里发布以澄清情况.

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.
Run Code Online (Sandbox Code Playgroud)

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

Del*_*ics 27

假设你有一个合理的理由这样做(并且使用TComponent很可能你会这样做 - 看到答案的结尾为什么),然后问题发生在你破坏了一个接口变量的引用之后它目前引用的对象.

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

  intfA := intfB;
Run Code Online (Sandbox Code Playgroud)

变得(简单来说):

  if Assigned(intfA) then
    intfA.Release;

  intfA := intfB;

  if Assigned(intfA) then
    intfA.AddRef;
Run Code Online (Sandbox Code Playgroud)

如果您将其与您的代码相关联,您应该会看到问题:

  SomeInterface.AsObject.Free;
  SomeInterface := nil;  
Run Code Online (Sandbox Code Playgroud)

变为:

SomeInterface.AsObject.Free;

if Assigned(SomeInterface) then
  SomeInterface.Release;

SomeInterface := nil;  

if Assigned(SomeInterface) then
  SomeInterface.AddRef;
Run Code Online (Sandbox Code Playgroud)

因此,您可以看到生成的对Release()的调用是通过将NIL分配给导致访问冲突的接口而产生的.

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

obj := SomeInterface.AsObject;
SomeInterface := NIL;
obj.Free;
Run Code Online (Sandbox Code Playgroud)

这里的关键问题是为什么你明确地释放了一个接口的对象(并且可能是引用计数).

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

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

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

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

如果这些条件中的第一个对你来说没有多大意义,那么在不希望光顾的情况下,这可能是一个很好的迹象,你不应该明确地释放该对象,因为它几乎肯定是通过引用计数来管理的.

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

  • 非常感谢,Deltics.你关于逆转释放代码的建议听起来会解决我的问题.而且它非常了解界面如何工作,我从你的答案中学到了很多东西.再次感谢. (2认同)
  • +1.虽然你可以在类中禁用引用计数(通过提供`_AddRef`和`_Release`的实现,它们根本不计算任何东西,你不能禁用编译器生成*调用*到引用计数函数.并且因为函数总是要被调用,你需要确保在你破坏接口变量所引用的对象之前没有剩余的引用*,即使你不打算*计算引用. (2认同)
  • @deltics我很想听听TInterfacedObject析构函数中有关bug的更多信息. (2认同)

Mik*_*son 5

您不应该使用TComponent作为接口对象的基类,而应该使用TInterfacedObject.TInerfacedObject已经实现了必要的功能来处理Delphi中接口的生命周期管理.您也不应该将访问界面作为界面和对象混合使用.这是对代码的修改,可以很好地工作,没有内存泄漏.

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.
Run Code Online (Sandbox Code Playgroud)

  • 亲爱的,@大卫.接口问题更明显.对于普通的对象引用,在释放对象之前,实际上不必清除所有引用.您只需要确保不使用*剩余变量的剩余值.但是对于接口,即使*程序员*不再使用陈旧的接口变量,*编译器*也会在变量超出范围或重新分配时隐式使用它.由于问题是由您无法看到的代码触发的,因此更容易忘记它潜伏并导致崩溃. (3认同)