Утечка памяти при использовании WMI в Delphi 7 - PullRequest
5 голосов
/ 25 июня 2009

Я испытываю утечку памяти при использовании WMI из Delphi 7 для запроса (удаленного) компьютера. Утечка памяти происходит только в 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 назначается объектам, в отличие от духа Дельфи ... Это 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.

Ответы [ 2 ]

7 голосов
/ 25 июня 2009

Я могу воспроизвести поведение, код утечки памяти на Windows XP 64 и не на Windows XP. Интересно, что это происходит, только если свойство Path_ читается, чтение Properties_ или Security_ с тем же кодом не приводит к утечке памяти. Проблема, связанная с версией Windows в WMI, выглядит наиболее вероятной причиной этого. Моя система соответствует современным требованиям AFAIK, поэтому, вероятно, для этого также нет исправления.

Я бы хотел прокомментировать, как вы сбрасываете все переменные варианта и интерфейса. Вы пишете

Вы заметите, несколько раз, что nil назначается объектам, в отличие от духа Delphi ... Это COM-объекты, которые не наследуются от TObject и не имеют деструктора, который я могу вызвать. Назначив им 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), а само уничтожение действительно должно произойти именно в этот момент. Примеры последнего: освобождение большого куска памяти или закрытие файла или освобождение объекта синхронизации.

0 голосов
/ 25 июня 2009

вы должны хранить возвращаемое значение

sObject.Path_;

в переменной и сделайте это SWbemObjectPath. Это необходимо для правильного подсчета ссылок.

...