无法释放 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
你可能会说你写的是一个函数,而不是程序。
然而,这只是一个语法糖。
就像 TDateTime
和 double
是不同的术语,但这些术语是同一实现的同义词;
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 管理的类型,以及 record
、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;
下面的函数接受 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
你可能会说你写的是一个函数,而不是程序。
然而,这只是一个语法糖。
就像 TDateTime
和 double
是不同的术语,但这些术语是同一实现的同义词;
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 管理的类型,以及 record
、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;