windows – 使用Delphi 7中的WMI进行内存泄漏

当使用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精神相反……这些是不从TObject继承的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,'',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,所以可能没有这个的修补程序.

不过,我想评论一下您重置所有变量和接口变量.你写

You’ll notice on several occasions,nil is assigned to objects,contrary to the Delphi spirit… These are COM objects that do not inherit from TObject,and have no destructor I can call. By assigning nil to them,Windows’s garbage collector cleans them up.

这不是真的,因此不需要将变量设置为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,Value) = S_OK) do
  begin
    sObject := IUnkNown(tempObj) as SWBemObject;
    { Leak! }
    sObject.Path_;
  end;
end;

我会说只有当这实际上释放了对象时才显式重置接口(因此当前引用计数必须为1)并且破坏本身应该恰好在此时发生.后者的示例是可以释放大块内存,或者需要关闭文件或释放同步对象.

相关文章

Windows2012R2备用域控搭建 前置操作 域控主域控的主dns:自...
主域控角色迁移和夺取(转载) 转载自:http://yupeizhi.blo...
Windows2012R2 NTP时间同步 Windows2012R2里没有了internet时...
Windows注册表操作基础代码 Windows下对注册表进行操作使用的...
黑客常用WinAPI函数整理之前的博客写了很多关于Windows编程的...
一个简单的Windows Socket可复用框架说起网络编程,无非是建...