当使用Delphi 7的WMI查询(远程)PC时,我遇到内存泄漏。内存泄漏仅发生在Windows 2003(和Windows XP 64)上。 Windows 2000很好,Windows 2008也是如此。我想知道是否有人遇到过类似的问题。
泄漏仅发生在某些版本的Windows中这意味着它可能是一个Windows问题,但我一直在搜索网络,但无法找到修补程序来解决问题。此外,它可能是一个Delphi问题,因为在C#中具有类似功能的程序似乎没有这种泄漏。后一个事实使我相信可能有另一种更好的方法来获取Delphi中所需的信息而不会导致内存泄漏。
我已将源代码包含在一个小程序中,以揭示下面的内存泄漏。如果执行sObject.Path_
注释下面的行{ Leak! }
,则会发生内存泄漏。如果我发表评论,那就没有泄漏。 (显然,在“真实”程序中,我使用sObject.Path_
方法调用的结果做了一些有用的事情:)。
在我的机器上进行了一些快速的Windows任务管理器分析,我发现了以下内容:
Before N=100 N=500 N=1000 With sObject.Path_ 3.7M 7.9M 18.2M 31.2M Without sObject.Path_ 3.7M 5.3M 5.4M 5.3M
我想我的问题是:还有其他人遇到过这个问题吗?如果是这样,它确实是Windows问题,是否有修补程序?或者(更有可能)我的Delphi代码被破坏了,有没有更好的方法来获取我需要的信息?
您会注意到,nil
被分配给对象,与Delphi精神相反......这些是COM对象,它们不从TObject
继承,并且没有析构函数我可以呼叫。通过为它们分配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.
答案 0 :(得分:7)
我可以重现行为,代码在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)并且破坏本身应该恰好在此时发生。后者的例子是可以释放大块内存,或者需要关闭文件或释放同步对象。
答案 1 :(得分:0)
你应该存储
的返回值sObject.Path_;
在变量中并使其成为SWbemObjectPath。这对于使引用计数正确是必要的。