2009-06-25 1 views
5

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

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

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

С небольшим быстрым «грязным профилированием профилей Windows Task Manager» на моей машине я нашел следующее:

 
         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 ... Это 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. 

ответ

7

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

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

Вы заметите несколько раз, ноль присваиваются объекты, в отличие от 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

Возможно, вы правы, но явное перенаправление переменных устранило утечку памяти. Может быть, я немного переборщил, сбросив абсолютно все, но эй, утечки памяти еще не все ушли :). Спасибо за воспроизведение ошибки и сообщения об этом, хотя! – jqno

0

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

sObject.Path_; 

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

+0

Спасибо за ваш ответ! К сожалению, это не сработало. Я объявил «var Path: SWbemObjectPath;» и присвоил ему значение «sObject.Path_». Объем памяти остается неизменным, независимо от того, изменена ли переменная Path или нет. – jqno

+0

Неверно, управление счетчиком ссылок не требует назначения переменной, оно работает так же хорошо без него. Если компилятор не оптимизирует его, это просто добавит еще пару _AddRef() и _Release(). – mghie