【问题标题】:Unable to free IXMLDocument, IXMLNodeList or maybe other (omnixml's) interface type objects [duplicate]无法释放 IXMLDocument、IXMLNodeList 或其他(omnixml 的)接口类型对象 [重复]
【发布时间】:2016-12-07 19:45:59
【问题描述】:

下面的函数接受 XML 输入,对其进行解析,并返回一个普通字符串,该字符串简单地显示在调用者函数中。因此上下文中的对象是下面函数的内部对象。

但是这个函数有一个奇怪的问题,它记住了输入,这意味着对象没有被正确释放。即使输入被检查为不同,输出字符串也有一部分是由上一次调用产生的。

在将 XMLDoc、IXMLNodeList 中的每一个都分配给 nil 之前,我还尝试通过将 nil 分配给它来在循环中释放从 Item 数组直接引用的每个 IXMLNode,但是该分配语句导致语法错误,因此解决了以下问题.

function tform1.nodeToSentence(nodeXml: string): string ;
var
  tempXmlDoc : IXMLDocument;
  resultWordPuncNodes : IXMLNodeList;
  i: Integer;

begin
  tempXmlDoc := CreateXMLDoc;
  tempXmlDoc.LoadXML(nodeXml);
  resultWordPuncNodes := XPathSelect(tempXmlDoc.DocumentElement,'//*');

  for i:= 0 to resultWordPuncNodes.Length-1   do
  begin

    if resultWordPuncNodes.Item[i].NodeName = 'name' then
     begin
       if resultWordPuncNodes.Item[i].Attributes['attrib'] = 'v_attrib' then
       begin
          Result := TrimRight(Result);
          Result := Result + resultWordPuncNodes.Item[i].Text + ' ';
       end
       else Result := Result + resultWordPuncNodes.Item[i].Text + ' ';
     end;
  end;


  resultWordPuncNodes:=nil;
  tempXmlDoc := nil; //interface based objects are freed this way
end;

调用代码

//iterating over i   
          nodeXML := mugList.Strings[i];
          readableSentence := nodeToSentence(mugList.Strings[i]);
          //examplesList.Append(readableSentence);
          //for debugging
          showMessage(readableSentence);
 //iteration ends

【问题讨论】:

  • 我已经添加了 CALLING 部分。
  • 是的,你忘了清除 readableSentence 变量,就是这样,它与 XML 无关

标签: delphi delphi-xe5


【解决方案1】:

但是这个函数有一个奇怪的问题,它记住了输入,这意味着对象没有被正确释放。即使输入被检查为不同,输出字符串也有一部分是由上一次调用产生的。

这是因为 function(以及其他 ARC 管理的类型,以及 recordobject 和方法指针)的 String 返回值在调用者和被调用者之间使用隐藏var 参数NOT在输入函数时自动清除,正如您所期望的那样。

这段代码:

function tform1.nodeToSentence(nodeXml: string): string ;
...
readableSentence := nodeToSentence(mugList.Strings[i]);

实际上与此代码相同:

procedure tform1.nodeToSentence(nodeXml: string; var Result: string);
...
nodeToSentence(mugList.Strings[i], readableSentence);

多次调用nodeToSentence(),您可能会将越来越多的文本附加到同一个String 变量中。

在函数内部,您需要手动重置 Result 值,然后再开始将新值连接到它:

function tform1.nodeToSentence(nodeXml: string): string ;
var
  ...
begin
  Result := ''; // <-- add this!
  ...
end;

【讨论】:

  • 雷米,拜托。 1)不要编辑我的代码。 2)当我正在编辑它时不要编辑它。 3) 不要将正确的代码更改为损坏的代码。将 String 变量分配给 nil 甚至不会编译!
  • 我改变的唯一是把Result := nil改为Result := ''。正如您自己所说,“您不能将 NIL 分配给字符串变量”,所以我只是为您修复了一个错误。
  • 好吧,我看错了编辑日志。确实,#3 是我的错误,抱歉。但是其余的都成立,当我进行长时间的编辑时,突然告诉我我无法保存它。很烦人。在这里稍加评论就足够了:-)
  • 好吧,做“更多”的编辑,而这个人正在做他自己的事情是......阴暗的。我的回答也提供了更多信息并涉及更多问题。你也在不断扩展你自己最初的简短答案,我明白了
  • @Arioch'The:它发生在所有 ARC 类型中,以确保正确的 ARC 语义。如果调用者没有将结果分配给真实变量,则使用隐藏变量。
【解决方案2】:

它与接口无关。 它只有在 Delphi 中的字符串实现。

您必须清除 Result 变量作为函数的第一行。

下面是你的固定功能:

function tform1.nodeToSentence(nodeXml: string): string ;
var
  tempXmlDoc : IXMLDocument;
  resultWordPuncNodes : IXMLNodeList;
  i: Integer;

begin

// Change #1 - added the line
  Result := ''; 
// Variable Result is shared here before by both the function and the caller.
// You DO HAVE to clear the shared variable to make the function FORGET the previous result.
// You may do it by the 'function' or by the calling site, but it should have be done.
// Usually it is better to do it inside the function.

  tempXmlDoc := CreateXMLDoc;
  tempXmlDoc.LoadXML(nodeXml);
  resultWordPuncNodes := XPathSelect(tempXmlDoc.DocumentElement,'//*');

  for i:= 0 to resultWordPuncNodes.Length-1   do
  begin

    if resultWordPuncNodes.Item[i].NodeName = 'name' then
     begin
       if resultWordPuncNodes.Item[i].Attributes['attrib'] = 'v_attrib' then
       begin

/// REMEMBER this line, it is important, I would explain later.
          Result := TrimRight(Result);
          Result := Result + resultWordPuncNodes.Item[i].Text + ' ';
       end
       else Result := Result + resultWordPuncNodes.Item[i].Text + ' ';
     end;
  end;

  resultWordPuncNodes:=nil;

// Change #2 - removed the line - it is redundant
 (* tempXmlDoc := nil; //interface based objects are freed this way *)
// Yes, they are. But Delphi automatically clears local ARC-variables 
//   when destroying them where the function exits.
// Variable should both be local and should be one of ARC types foe that.
end;

1) 关于自动清除本地 ARC 类变量见http://docwiki.embarcadero.com/Libraries/XE8/en/System.Finalize

2) 你真正的函数根本不是函数。

// function tform1.nodeToSentence(nodeXml: string): string ;   // only an illusion  
procedure tform1.nodeToSentence(nodeXml: string; var Result: string);  // real thing

您可能会说您正在编写一个函数,而不是过程。 然而,这只是一个语法糖。 就像TDateTimedouble 是不同的术语,但这些术语是相同实现的同义词;

Delphi 中所有返回 String/AnsiString/UnicodeString 变量的函数都是过程。它们只是伪装成功能,伪装很薄。

3) 关于它有一个古老的 Delphi kōan。如果没有 OmniXML 和所有只会模糊事实的复杂东西。运行这段代码,看看它的行为。

Function Impossible: String;
begin
  ShowMessage( 'Empty string is equal to: ' + Result );
end;

Procedure ShowMe; Var spell: string;
begin
  spell := Impossible();

  spell := 'ABCDE';
  spell := Impossible();

  spell := '12345';
  spell := Impossible();
end;

4) 现在,你知道吗?是的,你可以,它只需要一点点注意力。让我们做一点改变。

Function ImpossibleS: String;
begin
  ShowMessage( 'Unknown string today is equal to: ' + Result );
end;

Function ImpossibleI: Integer;
begin
  ShowMessage( 'Unknown integer today is equal to: ' + IntToStr(Result) );
end;


Procedure ShowMe; 
Var spell: string; chant: integer;
begin
  spell := ImpossibleS();

  spell := 'ABCDE';
  spell := ImpossibleS();

  spell := '12345';
  spell := ImpossibleS();

  chant := ImpossibleI();

  chant := 100;
  chant := ImpossibleI();

  chant := 54321;
  chant := ImpossibleI();
end;

注意并阅读编译警告。您确实修复了它编译时没有编译器警告的原始代码,不是吗?

现在编译我的第二个公案。阅读警告。整数函数确实会生成“非初始化变量”警告。字符串函数没有。是这样吗?

为什么不一样?因为字符串是 ARC 类型的。这意味着字符串函数实际上是一个过程,而不是一个函数。这意味着 Result 变量由调用者在外观函数之外初始化。您可能还观察到chant 变量在调用后确实发生了变化,因为整数函数是一个真正的函数,而调用后的赋值是一个真正的函数。相反,字符串函数是一种虚幻的函数,调用后的虚幻赋值也是如此。它看起来像已分配,但实际上并非如此。

5) 最后一件事。为什么我在你的代码中加上那个标记。

/// REMEMBER this line, it is important, I would explain later.
          Result := TrimRight(Result);

正是因为上面那个kōan。你一定是在看这里的垃圾。您一定一直在使用您之前未在任何地方初始化的“结果”变量。您一定已经预料到您的编译器会在这一行向您发出警告。就像上面的ImpossibleI 真实函数一样。但事实并非如此。就像它没有 ImpossibleS 幻觉功能一样。这种缺乏警告是德尔福在这里创造的错觉的一个致命的赠品。您是否注意到应该有一个“未初始化的变量”警告但它丢失了,您会问自己“如果它不是我的函数,谁初始化了变量”,您就会明白自己发生了什么。 ;-)

6) 好的,最后一件事。

procedure StringVar( const S1: string; var S2: string );
begin
  ShowMessage ( S1 + S2 );
end;

procedure StringOut( const S1: string; out S2: string );
begin
  ShowMessage ( S1 + S2 );
end;

这两个过程看起来很相似。不同的是S2种。它应该是 OUT 参数,而不是 VAR (IN+OUT) 参数。但是德尔福在这里失败了,只是因为你的功能。 Delphi 不能做字符串类型的 OUT 参数。作为比较,FreePascal(Lazarus,CodeTyphon) 知道其中的区别,并会为 StringOut 过程显示合法的“未初始化变量”警告。

【讨论】:

  • 如果我不关心被调用函数中传递的'Result'参数的清除,它不会在第一行被清除(Result = nil)吗?
  • 您不能将 NIL 分配给字符串变量。是的,如果您分配给它,它将被清除。但是您没有分配给它。就是这样。如果你想清除它,你必须清除它。在函数内部或外部,但你必须这样做。
  • 洞察问题(字符串实际上返回一个 'var' 参数)、垃圾来源(Result := TrimRight(Result))、关于释放 XMLDoc 的提示以及所有基本内容(koan 方法, ARC 类型、编译器警告等)使其成为出色的答案。对以后的读者很有帮助。
  • 可悲的是,我们越来越习惯于“永恒的九月”类型的读者。那些抱着“只是修复代码,别让我思考,这很痛苦”的态度的人。看到喜欢主动思考和探索的新读者真是令人耳目一新。希望你最终能成为 SO 的常客 :-D
  • OTOH,未来的读者永远不会找到它,他们会认为这个问题是关于 OmniXML 的,而只是跳过它,他们会用其他一些代码和库来陷入这个陷阱:-D
猜你喜欢
  • 2014-01-10
  • 2013-01-12
  • 2016-02-07
  • 2020-05-25
  • 1970-01-01
  • 1970-01-01
  • 2014-08-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多