【问题标题】:Memory leak using WMI in Delphi 7在 Delphi 7 中使用 WMI 的内存泄漏
【发布时间】:2009-06-25 14:19:25
【问题描述】:

我在使用 Delphi 7 中的 WMI 查询(远程)PC 时遇到内存泄漏。内存泄漏仅发生在 Windows 2003(和 Windows XP 64)上。 Windows 2000 很好,Windows 2008 也很好。我想知道是否有人遇到过类似的问题。

泄漏仅发生在某些版本的 Windows 中的事实意味着它可能是 Windows 问题,但我一直在搜索网络,但无法找到解决问题的修补程序。此外,这可能是 Delphi 问题,因为在 C# 中具有类似功能的程序似乎没有这种泄漏。后一个事实使我相信,在 Delphi 中可能有另一种更好的方法来获取我需要的信息,而不会导致内存泄漏。

我已经包含了一个小程序的源代码,以暴露下面的内存泄漏。如果执行{ Leak! } 注释下方的sObject.Path_ 行,就会发生内存泄漏。如果我将其注释掉,则没有泄漏。 (显然,在“真实”程序中,我对sObject.Path_ 方法调用的结果做了一些有用的事情:)。)

通过在我的机器上快速进行 Windows 任务管理器分析,我发现了以下内容:

之前 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 精神背道而驰……这些 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.

【问题讨论】:

    标签: windows delphi com memory-leaks wmi


    【解决方案1】:

    我可以重现该行为,该代码在 Windows XP 64 上会泄漏内存,而在 Windows XP 上则不会。有趣的是,只有在读取Path_ 属性时才会发生这种情况,使用相同的代码读取Properties_Security_ 不会泄漏任何内存。 WMI 中特定于 Windows 版本的问题看起来是最可能的原因。我的系统是最新的 AFAIK,因此可能也没有针对此的修补程序。

    不过,我想对您重置所有变体和接口变量发表评论。你写

    你会注意到有好几次,nil 被分配给对象,这与 Delphi 精神背道而驰……这些是 COM 对象,不继承自 TObject,并且没有我可以调用的析构函数。通过将 nil 分配给它们,Windows 的垃圾收集器会清理它们。

    这不是真的,因此不需要将变量设置为nilUnassigned。 Windows 没有垃圾收集器,您正在处理的是引用计数对象,一旦引用计数达到 0,这些对象就会立即销毁。Delphi 编译器确实插入必要的调用以根据需要增加和减少引用计数。您对nilUnassigned 的赋值会减少引用计数,并在达到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)并且销毁本身应该在这一点上真正发生时,才应该显式地重置接口。后者的示例是可以释放大量内存,或者需要关闭文件或释放同步对象。

    【讨论】:

    • 您可能是对的,但显式重置变量确实解决了另一个内存泄漏问题。也许我通过完全重置所有内容有点过火了,但是,内存泄漏还没有完全消失:)。不过,感谢您重现错误并报告它!
    【解决方案2】:

    你应该存储

    的返回值
    sObject.Path_;
    

    在一个变量中并使其成为 SWbemObjectPath。这是使引用计数正确的必要条件。

    【讨论】:

    • 感谢您的回答!不幸的是,它没有用。我声明了一个var Path: SWbemObjectPath; 并将sObject.Path_ 的返回值分配给它。无论我是否将 Path 变量设为 nil,内存占用量都保持不变。
    • 不正确,引用计数的管理不需要分配给变量,没有它也一样。除非编译器将其优化掉,否则只会添加另一对 _AddRef() 和 _Release()。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-18
    相关资源
    最近更新 更多