【问题标题】:How to find a dangling interface that causes an AV in Delphi如何在 Delphi 中找到导致 AV 的悬空接口
【发布时间】:2011-03-09 12:51:02
【问题描述】:

我有一个复杂的应用程序,我刚刚对其进行了一些更改,添加了几个具有接口的新类并删除了一些其他类。从功能上讲,一切正常,但在类的 Destroy 过程之后我遇到了访问冲突:

“模块‘xxxx.exe’中地址0040B984的访问冲突。读取地址80808088”。

我知道这在类的“Finalize”代码中,如果我进入反汇编(Delphi 2010),我可以看到 AV 的重点。不过,我看不到一种简单的方法来找出我的哪些变量触发了这种情况。在深入研究时,是否有一个程序可以让我对所引用的实例有所了解?

谢谢 布赖恩

【问题讨论】:

  • 我建议使用free而不是destroy。

标签: delphi debugging interface access-violation


【解决方案1】:

在大多数情况下,此类错误可以通过使用FastMM 并使用条件定义FullDebugModeCatchUseOfFreedInterfaces 编译应用程序来捕获。只需确保将 FastMM4 放在 dpr 的“使用”列表中的第一位即可。

【讨论】:

  • 好建议 gabr,我正在使用 FastMM 但没有打开“CatchUseOfFreedInterfaces”。现在,这给了我一个整齐的堆栈我之前跟踪的步骤,但我并没有更接近于“谁持有”非法引用的线索。我正在做我自己的引用计数(-1),因为我正在释放我自己的类(就像 TComponent 一样),所以我需要知道非法接口指针是从哪里得到的。布里
  • 几周前我遇到了类似的问题,这是由混合引用和非引用计数接口引起的。我不知道为什么这会导致问题,但确实如此。
【解决方案2】:

查找问题的步骤:

  1. 按照 Gabr 的建议在完全调试模式下使用 FastMM(我想您已经这样做了,看看 808080 模式)。
  2. 在 Destroy 过程中将您在类中使用的所有接口显式设置为 nil
  3. 在 Destroy 过程的开头放置一个断点
  4. 现在逐步执行您的 Destroy 程序,当取消悬空接口时,您将获得访问冲突,并且您会知道它是哪个接口。
  5. 如果您在清空所有接口后仍然拥有 AV,则对父类执行步骤 2 - 5。

我也遇到过这些问题,上面的方法帮我找到了。我的问题是由实现接口的 TComponents 引起的。假设你有 ComponentA 和 ComponentB,ComponentB 实现了一个接口。您将 ComponentB(或其接口)分配给 ComponentA 并存储接口引用。现在 ComponentB 被销毁,但 ComponentA 不知道这一点。当您销毁 ComponentA 时,它会取消接口,调用 _Release 方法并获得 AV。

解决方案是使用 TComponent.FreeNotification。当您收到来自 ComponentB 的免费通知时,您将 ComponentA 中的接口设为 nil。我对您的代码一无所知,但如果您的问题类似,您也可以使用 FreeNotifications。

编辑:添加了第 5 步

【讨论】:

  • Fox:我会仔细检查我是否到处都有 nils!
  • @Brian Frost:你甚至知道哪个班级正在制作 AV 吗?将接口显式设置为 nil 非常简单,但在有很多接口时可能会很麻烦。
【解决方案3】:

此错误看起来像是您正在使用 FastMM 进行内存管理。 该错误表明您引用的指针已被 FastMM 清除为 DebugFillDWord 值。

这意味着你使用的接口引用了一个已经被释放的对象。
这也意味着你没有启用CatchUseOfFreedInterfaces

为了改变这些和调试,你不能使用 Delphi 附带的股票 FastMM。
您需要下载FastMM(4.94 版)。

下载后:

就像gabr 已经提到的那样,在FastMM4Options.inc 中,确保启用FullDebugModeCatchUseOfFreedInterfaces(这会禁用CheckUseOfFreedBlocksOnShutdown,但你现在对后者不感兴趣)。
您可能还想启用RawStackTraces;这取决于您当前的堆栈跟踪是否足够好。

完成这些设置后,然后通过调试器使用 FastMM 运行您的应用,并在 FastMM4 单元内的此方法上设置断点:

procedure TFreedObject.InterfaceError;

我稍微修改了我的 FastMM4 单元以获得更多上下文信息;我可以和你分享(我已经邮寄给 FastMM4 团队了,但它还没有包含在官方资源中)。

我写了一个非常密集的blog article on debugging using FastMM,可能会对你有所帮助。
如果需要进一步解释,请在此处添加注释:-)

祝你好运,如果您需要进一步的指示,请告诉我们。

--杰罗恩

编辑:20100701 - 强调了布赖恩评论中提到的内容。

【讨论】:

  • 谢谢,非常感谢您提到的上下文信息模式。已经在这里使用 FastMM 494,但我会按照你的博客步骤进行操作。布里
  • @brian:通过我的博客(或电子邮件)给我留言;在 pluimers.com 上几乎所有东西都可以使用,尤其是当您在 at 符号前使用我的名字时)
  • @All:这个答案的关键点是:“当你完成这些设置后,然后通过调试器使用 FastMM 运行你的应用程序,并在 FastMM4 单元内的这个方法上放置一个断点:procedure TFreedObject .InterfaceError”。确定有关生成的 TFreedObject 的更多信息(即它的类类型)的某种方法在 FastMM 中非常有用。
  • @BrianFrost 不久前这些更改使其进入了 FastMM4;查找FullDebugModeCallBacksbitbucket.org/jeroenp/besharp.net/src/tip/Native/Delphi/Library/… 的示例和差异github.com/pleriche/FastMM4/commit/…
【解决方案4】:

在您的代码中要寻找的一件事是这个

FInterfacedObject.GetInterface 

在同一范围内
FInterfacedObject := TInterfacedObjectClass.Create.

其中 FInterfacedObject 是一个类变量。

您可以根据需要从内部函数调用 GetInterface,但如果您在创建 FInterfacedObject 的同一范围内调用 GetInterface,无论出于何种原因,您都会将引用计数降至 0 并释放该事物,但它赢了'不为零,所以如果你这样做了

if assigned(FInterfacedObject) then
    FInterfacedObject.Free;

你会遇到访问冲突。

【讨论】:

    【解决方案5】:

    可以使用 EurekaLog 之类的工具吗? http://delphi.about.com/od/productreviews/ss/eurekalog.htm

    【讨论】:

      【解决方案6】:

      困扰我的一个类似错误是已在现有对象上设置的接口引用,当释放所有者对象时,接口引用计数器不会自动减少。它可以通过所有者对象的析构函数中的if Assigned(FMyInterface) then FMyInterface := nil; 来解决。

      【讨论】:

      • 明智的教训:如果您使用接口,请独占使用它们,让引用计数机制为您管理一切。尽可能避免使用 TComponent 后代上的接口,因为所有者/组件关系将与引用计数机制竞争,从而导致一个受害者:试图调试内存问题的人。
      【解决方案7】:

      我做了类似的事情,你对象的析构函数中的以下代码会有所帮助

      Destructor TMyObjectThatIAmDoingManualRefCounting.Destroy;
      begin
        if FMyRefCount<>0 then
          messageDlg('You dork, you called Free on me when someone still had references to me');
      
        inherited;
      end;
      

      然后你至少可以找出你在哪里不恰当地释放了对象。可能你释放它太早了。过早释放对象实际上不会导致问题,当您释放持有对已释放对象的接口引用的对象时,您将收到错误。

      您可以做的另一件事是在您的 addref 和 release 方法中设置断点,并跟踪谁在保留接口引用,以及这些相同的对象是否在之后释放它们。

      还有一个常见的问题如下,如果你得到一个接口并在同一个方法中释放对象

      var
        o:TSomeObject;
      begin
        o:=TSomeObject.Create;
        (o as ISomeInterface).DoSomething;
        o.free
      end;
      

      这将导致方法结束时出现 AV,因为编译器创建了一个假接口变量,该变量在方法结束时被释放。

      你需要这样做

      var
        o:TSomeObject;
        i:ISomeInterface;
      begin
        o:=TSomeObject.Create;
        i:=(o as ISomeInterface); // or Supports or whatever
        i.DoSomething;
        i:=nil;
        o.free
      end;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-05-12
        • 1970-01-01
        • 2012-06-14
        • 1970-01-01
        • 2011-03-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多