使用Delphi 7的WMI查询(远程)电脑时,遇到内存泄漏。 内存泄漏只发生在Windows 2003(和Windows XP 64)上。 Windows 2000是好的,Windows 2008也是如此。我想知道是否有人遇到类似的问题。
事实上,泄漏只发生在某些版本的Windows意味着它可能是一个Windows问题,但我一直在searchnetworking,一直没有能够find一个修复程序来解决问题。 另外,这可能是一个Delphi问题,因为在C#中具有类似function的程序似乎没有泄漏。 后一个事实使我相信,在Delphi中获得所需的信息可能还有另一种更好的方式,而不会发生内存泄漏。
我已经将源代码包含在一个小程序中,以显示下面的内存泄漏。 如果sObject.Path_
下面的行{ Leak! }
{ Leak! }
注释被执行,发生内存泄漏。 如果我把它评论出来,就没有泄漏。 (显然,在“真正的”程序中,我用sObject.Path_
方法调用的结果做了一些有用的sObject.Path_
。)
用我的机器上的一个简单的Windows任务pipe理器分析,发现了以下内容:
在N = 100之前N = 500 N = 1000 sObject.Path_ 3.7M 7.9M 18.2M 31.2M 没有sObject.Path_ 3.7M 5.3M 5.4M 5.3M
我想我的问题是:有其他人遇到过这个问题吗? 如果是这样,是不是Windows的问题,是否有一个修补程序? 或者(更可能)是我的Delphi代码被破坏了,有没有更好的方法来获取我需要的信息?
你会注意到有几次, nil
是分配给对象的,与Delphi精神相反……这些是不从TObject
inheritance的COM对象,也没有可以调用的析构函数。 通过给它们分配nil
,Windows的垃圾收集器清理它们。
program ConsoleMemoryLeak; {$APPTYPE CONSOLE} uses Variants, ActiveX, WbemScripting_TLB; const N = 100; WMIQuery = 'SELECT * FROM Win32_Process'; Host = 'localhost'; { Must be empty when scanning localhost } Username = ''; Password = ''; procedure ProcessObjectSet(WMIObjectSet: ISWbemObjectSet); var Enum: IEnumVariant; tempObj: OleVariant; Value: Cardinal; sObject: ISWbemObject; begin Enum := (wmiObjectSet._NewEnum) as IEnumVariant; while (Enum.Next(1, tempObj, Value) = S_OK) do begin sObject := IUnknown(tempObj) as SWBemObject; { Leak! } sObject.Path_; sObject := nil; tempObj := Unassigned; end; Enum := nil; end; function ExecuteQuery: ISWbemObjectSet; var Locator: ISWbemLocator; Services: ISWbemServices; begin Locator := CoSWbemLocator.Create; Services := Locator.ConnectServer(Host, 'root\CIMV2', Username, Password, '', '', 0, nil); Result := Services.ExecQuery(WMIQuery, 'WQL', wbemFlagReturnImmediately and wbemFlagForwardOnly, nil); Services := nil; Locator := nil; end; procedure DoQuery; var ObjectSet: ISWbemObjectSet; begin CoInitialize(nil); ObjectSet := ExecuteQuery; ProcessObjectSet(ObjectSet); ObjectSet := nil; CoUninitialize; end; var i: Integer; begin WriteLn('Press Enter to start'); ReadLn; for i := 1 to N do DoQuery; WriteLn('Press Enter to end'); ReadLn; end.
我可以重现的行为,代码泄漏在Windows XP 64上的内存,而不是在Windows XP上。 有趣的是,只有在读取Path_
属性的情况下,才会发生这种情况,读取Properties_
或Security_
具有相同的代码不会泄漏任何内存。 WMI中特定于Windows版本的问题看起来是最可能的原因。 我的系统是最新的AFAIK,所以可能没有这个修补程序。
不过,我想评论一下你重置所有的变体和接口变量。 你写
你会注意到有几次,nil是分配给对象的,与Delphi精神相反……这些是不从TObject继承的COM对象,也没有可以调用的析构函数。 通过给它们分配nil,Windows的垃圾收集器清理它们。
这是不正确的,因此不需要将变量设置nil
和Unassigned
。 Windows没有垃圾收集器,所涉及的是引用计数的对象,一旦引用计数达到0,就立即销毁引用计数的对象.Delphi编译器确实会插入必要的调用,以根据需要递增和递减引用计数。 您分配给nil
和Unassigned
任务递减引用计数,并在对象达到0时释放该对象。
一个新的赋值给一个变量,或者退出这个过程也会照顾到这个,所以额外的赋值(虽然没有错)是多余的,降低了代码的清晰度。 下面的代码是完全等效的,不会泄漏任何额外的内存:
procedure ProcessObjectSet(WMIObjectSet: ISWbemObjectSet); var Enum: IEnumVariant; tempObj: OleVariant; Value: Cardinal; sObject: ISWbemObject; begin Enum := (wmiObjectSet._NewEnum) as IEnumVariant; while (Enum.Next(1, tempObj, Value) = S_OK) do begin sObject := IUnknown(tempObj) as SWBemObject; { Leak! } sObject.Path_; end; end;
我会说,只有在实际上释放对象的情况下才能显式重置接口(所以当前的引用计数必须是1),并且销毁本身应该确切地发生在这一点上。 后者的例子是可以释放大量内存,或者需要关闭文件或释放同步对象。
你应该存储返回值
sObject.Path_;
在一个变量中,并使其成为SWbemObjectPath。 这是使参考计数正确的必要条件。