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

Delphi 无法释放IXMLDocument、IXMLNodeList或其他(omnixml';s)接口类型对象,delphi,delphi-xe5,Delphi,Delphi Xe5,下面的函数接收XML输入,对其进行解析,并返回一个普通字符串,该字符串仅显示在调用方函数中。因此,上下文中的对象是下面函数的内部对象 但是这个函数有一个奇怪的问题,它会记住输入,这意味着对象没有被正确释放。即使输入被检查为不同,输出字符串也有前一次调用产生的部分 在将XMLDoc、IXMLNodeList中的每一个都分配给nil之前,我还尝试在一个循环中通过将nil分配给它来释放直接从Item数组引用的每个IXMLNode,但该分配语句导致了一个语法错误,因此解决了以下问题 function t

下面的函数接收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

它与接口无关。 它在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) 关于自动清除局部变量和圆弧类变量,请参见

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

// function tform1.nodeToSentence(nodeXml: string): string ;   // only an illusion  
procedure tform1.nodeToSentence(nodeXml: string; var Result: string);  // real thing
你可以说你在写一个函数,而不是过程。 然而,这仅仅是一种句法上的糖分。 就像
TDateTime
double
是不同的术语,但这些术语是相同实现的同义词

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;
注意并阅读编译警告。您确实修复了它编译时没有编译器警告的原始代码,不是吗

现在编译我的第二个kōan。阅读警告。整数函数不会生成“未初始化变量”警告。字符串函数不支持。是这样吗

为什么不同?因为字符串是圆弧类型。这意味着字符串函数实际上是一个过程,而不是一个函数。这意味着结果变量由调用者在looks-like函数之外初始化。您还可以观察到
chant
变量在调用后确实会发生变化,因为整数函数是实函数,而调用后的赋值是实函数。相反,字符串函数是虚幻的,调用后的虚幻赋值也是虚幻的。它看起来是被分配的,但不是

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

/// REMEMBER this line, it is important, I would explain later.
          Result := TrimRight(Result);
正是因为上面的那个kōan。你一定是在这里读垃圾。您一定使用了“Result”变量,而您以前在任何地方都没有初始化该变量。您一定期望编译器在这一行向您发出警告。就像上面的实函数一样。但事实并非如此。就像它不具有不可能的幻觉功能一样。这种警告的缺失是德尔福在这里创造的幻觉的一个致命的赠品。您是否注意到应该有一个“未初始化的变量”警告,但它丢失了?您会问自己“如果不是我的函数,是谁初始化了变量”,您自己就会明白发生了什么事。;-)

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;
这两个过程看起来很相似。区别是两种。它应该是一个OUT参数,而不是VAR(IN+OUT)参数。但是Delphi在这里仅仅使用了您的函数就失败了。Delphi不能做字符串类型输出参数。要进行比较,FreePascal(Lazarus,CodeTyphon)知道差异,并将为
StringOut
过程显示合法的“未初始化变量”警告

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

这是因为
函数
字符串
返回值(以及其他ARC管理类型,以及
记录
对象
和方法指针)在调用者和被调用者之间通过使用隐藏的
变量
参数传递,该参数在输入函数时不会自动清除,正如你所期待的

此代码:

function tform1.nodeToSentence(nodeXml: string): string ;
...
readableSentence := nodeToSentence(mugList.Strings[i]);
实际上与本规范相同:

procedure tform1.nodeToSentence(nodeXml: string; var Result: string);
...
nodeToSentence(mugList.Strings[i], readableSentence);
多次调用
nodeToSession()
,可能会在同一
字符串
变量中追加越来越多的文本

在函数内部,您需要手动重置
结果
值,然后才能开始将新值连接到该值:

function tform1.nodeToSentence(nodeXml: string): string ;
var
  ...
begin
  Result := ''; // <-- add this!
  ...
end;
函数tform1.nodeToSession(nodeXml:string):string;
变量
...
开始

结果:='';//我已经添加了调用部分。是的,您忘记清除
readable句子
变量,即;如果我不关心被调用函数中传递的'Result'参数的清除,它不是在第一行被清除了吗(Result=nil)?您不能将nil赋值给字符串变量。是的,如果分配给它,它将被清除。但你没有分配给它。就这样。如果你想清除它,你必须清除它。无论是在函数内部还是外部,但您必须这样做。深入了解问题(字符串返回实际上是一个'var'参数)、垃圾源(Result:=TrimRight(Result))、关于XMLDoc释放的提示,以及所有基本内容(koan方法、弧类型、编译器警告等),这是一个很好的答案。这将对未来的读者非常有帮助。遗憾的是,我们越来越习惯于“永恒的九月”这类读者。那些抱着“只要修改代码,不要让我思考,这会伤害我”的态度的人。看到喜欢主动思考和解释的新读者,真是令人耳目一新