无法释放 IXMLDocument、IXMLNodeList 或其他(omnixml 的)接口类型对象

Unable to free IXMLDocument, IXMLNodeList or maybe other (omnixml's) interface type objects

下面的函数接受 XML 输入,解析它,returns 一个普通的字符串,它简单地显示在调用函数中。因此上下文中的对象是下面函数的内部对象。

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

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

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

与接口无关。 它仅在 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 公案。没有 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;

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

现在编译我的第二个公案。阅读警告。整数函数确实会生成 "Non-initialized variable" 警告。字符串函数没有。是这样吗?

为什么不同?因为字符串是 ARC 类型的。这意味着 string-function 实际上是一个过程,而不是一个函数。这意味着 Result 变量由调用者在 looks-like-function 之外初始化。您可能还会观察到 chant 变量在调用后确实发生了变化,因为整数函数是一个真实的函数,调用后的赋值也是一个真实的函数。相反,字符串函数是一种虚幻的函数,虚幻的调用后赋值也是如此。看起来是赋值了,其实不是。

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

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

正是因为上面那个公案。你一定一直在阅读这里的垃圾。您一定一直在使用之前未在任何地方初始化的 "Result" 变量。您一定已经预料到您的编译器会在这一行向您发出警告。就像上面的 ImpossibleI 实函数一样。但事实并非如此。就像它不具有ImpossibleS幻觉功能一样。这种警告的缺失是 Delphi 在这里创造的幻觉的死角。您会注意到应该有一个 'non-initialized var' 警告但它丢失了吗?您会问问自己 "who initialized variable if it was not my function" 并且您自己会明白发生了什么。 ;-)

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 仅在您的函数中失败。 Delphi 不能做字符串类型的输出参数。为了进行比较,FreePascal(Lazarus,CodeTyphon) 知道其中的区别,并且会显示 StringOut 程序的合法 'non-initialized variable' 警告。

But this function has a strange problem that, it remembers the input which means that the objects are not released properly. Output string has a part resulting from the previous call even if the input is checked to be different.

那是因为 String return 的 function 值(以及其他 ARC 管理的类型,以及 recordobject , 和方法指针)使用隐藏的 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;