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

时间:2016-12-07 19:46:00

标签: delphi delphi-xe5

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

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

在为每个XMLDoc,IXMLNodeList分配nil之前,我还尝试在循环中释放每个IXMLNode,它们直接从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

2 个答案:

答案 0 :(得分:2)

  

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

这是因为String(以及其他ARC托管类型,以及functionrecord和方法指针)的object返回值使用隐藏的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 :(得分:1)

它与接口无关。 它只在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)有一个旧的德尔福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;

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

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

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

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

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

正是因为上面的kōan。你一定是在这里读垃圾了。您必须使用之前未初始化的“结果”变量。您必须期望您的编译器在此行向您发出警告。就像它在上面的ImpossibleI实函数中一样。但事实并非如此。就像它不具有ImpossibleS幻觉功能一样。缺乏这种警告是德尔福在这里创造的错觉。你会注意到它应该是一个'非初始化的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种。它应该是OUT参数,而不是VAR(IN + OUT)参数。但是Delphi只是在你的功能上失败了。 Delphi不能做字符串型的OUT参数。比较一下,FreePascal(Lazarus,CodeTyphon)知道差异,并会显示StringOut程序的合法“非初始化变量”警告。