Утечка памяти при использовании WMI в Delphi 7
-
20-08-2019 - |
Вопрос
Я испытываю утечку памяти при использовании 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. Это необходимо для правильного подсчета ссылок.