Вопрос

Я испытываю утечку памяти при использовании WMI из Delphi 7 для запроса (удаленного) компьютера. Утечка памяти происходит только в Windows 2003 (и Windows XP 64). Windows 2000 в порядке, как и Windows 2008. Мне интересно, сталкивался ли кто-либо с подобной проблемой.

Тот факт, что утечка происходит только в определенных версиях Windows, подразумевает, что это может быть проблема с Windows, но я искал в Интернете и не смог найти исправление, чтобы решить проблему. Кроме того, это может быть проблемой Delphi, так как программа с похожей функциональностью в C #, похоже, не имеет этой утечки. Последний факт заставил меня поверить в то, что может быть другой, лучший, способ получить нужную мне информацию в Delphi без утечки памяти.

Я включил источник в небольшую программу, чтобы раскрыть утечку памяти ниже. Если строка sObject.Path_ ниже комментария { Leak! } выполняется, происходит утечка памяти. Если я это прокомментирую, утечки нет. (Очевидно, что в программе & Quot; real & Quot; я делаю что-то полезное с результатом вызова метода nil:).)

С помощью небольшого и быстрого профилирования диспетчера задач 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, и есть ли лучший способ получить нужную мне информацию?

Вы заметите, что несколько раз TObject присваивается объектам, что противоречит духу Delphi ... Это COM-объекты, которые не наследуются от <=> и не имеют деструктора, который я могу вызвать. Назначив им <=>, сборщик мусора 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 XP 64 и не на Windows XP. Интересно, что это происходит только в том случае, если свойство Path_ читается, чтение Properties_ или Security_ с одинаковым кодом не приводит к утечке памяти. Проблема, связанная с версией Windows в WMI, выглядит наиболее вероятной причиной этого. Моя система соответствует современным требованиям AFAIK, поэтому, вероятно, для этого также нет исправлений.

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

  

Вы заметите, что в некоторых случаях nil назначается объектам, в отличие от духа Delphi ... Это COM-объекты, которые не наследуются от TObject и не имеют деструктора, который я могу вызвать. Назначив им nil, сборщик мусора Windows очищает их.

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

Другие советы

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

sObject.Path_;

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top