title: Memory leak using WMI in Delphi 7
body: <p>I'm experiencing a memory leak when using WMI from Delphi 7 to query a (remote) pc. The memory leak only occurs on Windows 2003 (and Windows XP 64). Windows 2000 is fine, and so is Windows 2008. I'm wondering if anyone has experienced a similar problem.</p>
  
  <p>The fact that the leak only occurs in certain versions of Windows implies that it might be a Windows issue, but I've been searching the web and haven't been able to locate a hotfix to resolve the issue. Also, it might be a Delphi issue, since a program with similar functionality in C# doesn't seem to have this leak. The latter fact has led me to believe that there might be another, better, way to get the information I need in Delphi without getting a memory leak.</p>
  
  <p>I've included the source to a small program to expose the memory leak below. If the line <code>sObject.Path_</code> below the <code>{ Leak! }</code> comment is executed, the memory leak occurs. If I comment it out, there's no leak. (Obviously, in the "real" program, I do something useful with the result of the <code>sObject.Path_</code> method call :).)</p>
  
  <p>With a little quick 'n dirty Windows Task Manager profiling on my machine, I found the following:</p>
  
  <pre>
                         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
  </pre>
  
  <p>I guess my question is: has anyone else encountered this problem? If so, is it indeed a Windows issue, and is there a hotfix? Or (more likely) is my Delphi code broken, and is there a better way to get the information I need?</p>
  
  <p>You'll notice on several occasions, <code>nil</code> is assigned to objects, contrary to the Delphi spirit... These are COM objects that do not inherit from <code>TObject</code>, and have no destructor I can call. By assigning <code>nil</code> to them, Windows's garbage collector cleans them up.</p>
  
  <pre><code>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.
  </code></pre>