You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi中接口对象数组引发崩溃问题求助

Delphi中TNoRefCountObject接口数组崩溃问题的分析与解决

问题场景

将继承自TNoRefCountObject的对象存储在接口类型动态数组中时,程序会出现崩溃。核心原因是_DynArrayClear在清理数组时,尝试通过_IntfClear释放接口,而此时对象已被手动释放,导致访问无效内存。

程序并非每次都会崩溃,但启用内存管理器后,执行SetLength(MyListArray, 0)时必然触发"调用已失效接口"的错误。若将数组定义为array of TMyList可正常运行,但业务中需通过返回IMyList接口的函数赋值数组,希望避免后续类型转换。

复现代码

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, Generics.Collections, System.Classes;

type
  IMyList=interface ['{815633B4-5407-4AED-9A93-44DA7E609801}']
  end;

  TMyList=class(TNoRefCountObject, IMyList)
  public
  end;

var
  MyListArray:array of IMyList;
begin
  try
    SetLength(MyListArray,10);

    for var i := 0 to 9 do
    begin
      MyListArray[i]:=TMyList.Create;
      FreeAndNil(MyListArray[i] as TMyList);
    end;

    SetLength(MyListArray,0);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

问题原因

  1. 接口数组的默认清理逻辑:动态数组的元素类型是IMyList接口,Delphi会按照接口类型的规则处理数组清理。当调用SetLength(MyListArray, 0)时,_DynArrayClear会遍历每个元素调用_IntfClear,这是接口类型数组的固有行为,与实现类是否为TNoRefCountObject无关。
  2. TNoRefCountObject的特性:TNoRefCountObject仅重写了_AddRef和_Release为空实现,禁用了引用计数的增减,但并没有改变接口类型的存储和清理逻辑。当对象被FreeAndNil手动释放后,数组中仍保留着指向已释放内存的接口指针,_IntfClear执行时会访问无效内存,触发崩溃。

解决方案

方案1:恢复引用计数管理

修改TMyList的实现,重新启用引用计数,让接口的生命周期由引用计数自动管理,无需手动释放:

type
  TMyList=class(TNoRefCountObject, IMyList)
  protected
    function _AddRef: Integer; override;
    function _Release: Integer; override;
  public
    destructor Destroy; override;
  end;

function TMyList._AddRef: Integer;
begin
  Result := AtomicIncrement(FRefCount);
end;

function TMyList._Release: Integer;
begin
  Result := AtomicDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;

destructor TMyList.Destroy;
begin
  // 自定义清理逻辑
  inherited;
end;

使用时无需手动调用FreeAndNil,数组清理时会通过_Release自动释放对象。

方案2:使用TInterfaceList管理接口集合

改用System.Classes.TInterfaceList替代动态数组,它专门用于管理接口对象,清理时会正确处理接口引用,避免手动释放的问题:

var
  MyList: TInterfaceList;
begin
  try
    MyList := TInterfaceList.Create;
    for var i := 0 to 9 do
    begin
      MyList.Add(TMyList.Create as IMyList);
      // 无需手动释放,TInterfaceList销毁时自动处理
    end;
  finally
    MyList.Free;
  end;
end;

方案3:手动置空接口引用后再清理数组

如果必须保留手动释放的逻辑,需在清理数组前将每个接口元素置为nil,让_IntfClear处理空引用:

for var i := 0 to High(MyListArray) do
begin
  FreeAndNil(MyListArray[i] as TMyList);
  MyListArray[i] := nil; // 关键步骤:置空接口指针
end;
SetLength(MyListArray,0);

内容的提问来源于stack exchange,提问作者MikeL

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 16:28:13