Delphi 对象、NIL 对象和接口

6
我正在寻找有关如何调试使用Delphi VCL中的MS XML包装器的应用程序崩溃的提示。我怀疑是内存损坏或某种在对象和接口之间发生的晦涩的恶魔般的事情,例如引用计数错误或堆损坏。问题实际上是:我该如何调试此类崩溃?
这段特定的代码在基本XmlIntf接口(IXMLNode)上进行了大量的内部使用和扩展。ISomethingCustom是一个扩展IXMLNode的接口。问题出现在我们在递归函数中崩溃的地方,该函数传递了一个ISomethingCustom,它也是IXMLNode(或在接口术语中支持)。
   boolean UtilityFunction( aNode: ISomethingCustom ):Boolean;
   begin
      if not Assigned(aNode) then exit; // this works. great.
      if not Assigned(aNode.ParentNode) then exit; // this DOES NOT WORK.
     // code that blows up if aNode.ParentNode is not assigned.
   end;

情况是aNode也是IXMLNode,而且IXMLNode.ParentNode的值被赋值(不为nil),但它指向的COM对象可能已经被释放、销毁或以某种方式损坏。我正在试图弄清楚当接口指针似乎有效但其背后的对象已被删除时发生了什么。
检查Assigned(aNode.ParentNode)返回TRUE,即使在调试器中尝试转换(仅在运行时,而不是在代码中)也是如此:
1.检查/评估aNode 2.检查/评估TInterfacedObject(aNode).ClassName(至少在Delphi 2010中有效!) 3.现在将TWhateverClassNameYouGotBefore(aNode)进行转换。 4.在调试器中,我现在看到这是NIL。这可能意味着Delphi 2010中新的“将接口转换回对象”的魔术功能失败了。
我认为我正在尝试调试一个堆损坏或COM对象在堆上损坏的问题,因为存在引用计数问题。
我真的认为没有人应该遇到接口似乎有效但底层对象已被删除的情况。我真的想知道该怎么做,以及发生了什么。

检查Assigned(aNode.ParentNode)返回TRUE,即使TNode(aNode).ParentNode实际上是NIL。 <-- 您是否将接口转换回对象引用? - The_Fox
一定是有什么地方出了问题。在发现问题之前,我并没有有意进行任何转换操作,然后在调试器的 evaluate-expression 中进行转换,只是为了查看接口背后的情况,看能否找到问题所在。 :-) - Warren P
3个回答

9
尽管您在代码中没有显示它,但您的注释似乎表明您正在将接口变量强制转换为类类型。这是不允许的。我已经解释了原因: 接口引用和对象引用指向的不是同一内容。因此,当编译器认为您拥有另一个引用时,在其中一个上调用方法将产生意想不到的结果。您很不幸,因为代码继续运行而没有崩溃,并且这表明您正在做错误的事情。
我的上面的文章总结了建议,如果您有一个由Delphi实现的接口并且该接口没有自己的函数来显示底层对象,则使用JCL中的JclSysUtils.GetImplementorOfInterface函数。

1
将接口指针强制转换回其实现类指针是Delphi 2010或XE中的一个新功能,我忘记是哪个版本引入的。 - Remy Lebeau
我之前将对象强制转换回Object是因为我注意到这样的转换会导致NIL值,并且觉得很奇怪。在运行时,我实际上并没有执行这样的强制转换,除了作为调试代码,但我很快就在发现你说的后删除了它。它仍然崩溃,这意味着我可能有堆损坏或其他问题。 - Warren P
我已经彻底重新写了我的问题,希望这样能更清楚地表达我的情况。你上面提到的"指针"让我明白了一些之前不理解的内容,非常感谢。考虑到我完全误解了这种情况,我想知道这个问题是否还能被解决,或者当我更了解情况时是否需要再次重写它。 - Warren P

0

我的建议是确保在Assigned(aNode.ParentNode)中实际调用了ParentNode函数。在Delphi中有一些令人讨厌的角落情况,其中没有参数的过程/函数不会被调用,而是在省略括号时取其引用。

尝试将其更改为Assigned(Anode.ParentNode())(应该与François的建议具有相同的效果)。


0
猜测一下:你是否尝试将aNode.ParentNode放入本地变量中,并在Utility函数的其余部分中使用它?
   function UtilityFunction(aNode: ISomethingCustom): Boolean;
   var
     lParentNode: INode;
   begin
      if not Assigned(aNode) then exit; // this works. great.
      lParentNode := aNode.ParentNode;
      if not Assigned(lParentNode) then exit;
     // code that uses lParentNode.
   end;

它看起来是一个有效的非nil接口指针,但当像TXMLNode(aNode)这样转换回对象时,你会得到nil。有时,在调试时可以工作,但在代码中却不行(代码看不到nil,但调试器可以)。棘手的问题。 - Warren P
1
大多数 Delphi 版本不允许将接口指针强制转换回对象指针。这是一个较新的功能,仅在 Delphi 2010 或 XE 中可用,我忘记哪个版本引入了它。在此之前,从接口指针获取对象指针的唯一方法是使接口实现返回实现类的 Self 指针的方法。 - Remy Lebeau
我刚学到这个。除了我在调试器中进行探索而发现您可以先找出类是什么,然后将其转换回类之外,我们没有故意进行任何此类转换。 - Warren P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接