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



下面的功能输入XML输入,解析并返回一个普通字符串,该字符串简单地显示在呼叫者函数中。因此上下文中的对象是下面函数的内部。

但是此功能有一个奇怪的问题,它记得输入,这意味着对象未正确释放。即使选中输入不同,输出字符串也具有上一个呼叫的零件。

在分配xmldoc,ixmlnodelist的每个nil之前,我还试图在循环中发布,每个IXMLNODE直接通过将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

但是此功能有一个奇怪的问题,它记得输入,这意味着对象未正确释放。即使选中输入不同,输出字符串也具有上一个呼叫的零件。

这是因为String的CC_1返回值(以及其他ARC管理的类型以及recordobject和方法指针)在Caller和Callee之间使用隐藏的var参数在呼叫者和Callee之间传递在输入功能时自动清除。

此代码:

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;

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

您必须将结果变量清除为功能的第一行。

以下是您的函数修复:

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-kind变量,请参见http://docwiki.embarcadero.com/libraries/xe8/xe8/en/system.finalize.finalize

2)您的实际功能根本不是功能。

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

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

delphi中的所有功能返回字符串/ansistring/unicodestring变量是过程。它们只是伪装成功能,伪装很薄。

3)关于它有一个古老的DelphiKō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;

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

现在编译我的第二个kōan。阅读警告。整数功能确实会产生"非启示变量"警告。字符串 - 功能没有。是吗?

为什么有区别?因为字符串是弧形。这意味着字符串功能确实是一个过程,而不是函数。这意味着结果变量是由呼叫者在外观样功能之外初始化的。您还可以观察到chant变量在调用后确实会发生变化,因为整数函数是一个真实的函数,而分配后呼叫是真实的函数。相反,弦乐功能是一种幻觉,幻觉分配后也是如此。看起来是分配的,但不是。

5)最后一件事。我为什么将该标记放在您的代码中。

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

正是由于上面的kōan。您一定在这里阅读垃圾。您必须使用以前没有初始化的"结果"变量。您必须期望您的编译器在此行向您发出警告。就像它在上面的ImpossibleI实际功能中一样。但这不是。就像ImpossibleS幻觉功能一样。缺乏警告是Delphi在这里创造的幻觉的死亡赠品。您是否会注意到应该有"非原始化的VAR"警告,但它丢失了您会问自己"谁不是我的功能,谁初始化了变量",您会明白自己发生了什么。; - )

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种。它应该是外部参数,而不是var(in out)参数。但是Delphi在您的功能上仅在这里失败。Delphi不能做字符串型外部参数。要比较,Freepascal(Lazarus,CodeTyphon)知道差异,并将显示StringOut程序的合法"非原始变量"警告。

最新更新