2009-06-25 13 views
5

Si verifica una perdita di memoria quando si utilizza WMI da Delphi 7 per interrogare un PC (remoto). La perdita di memoria si verifica solo su Windows 2003 (e Windows XP 64). Windows 2000 va bene, così come Windows 2008. Mi chiedo se qualcuno abbia riscontrato un problema simile.Perdita di memoria tramite WMI in Delphi 7

Il fatto che la perdita si verifichi solo in determinate versioni di Windows implica che potrebbe trattarsi di un problema di Windows, ma ho cercato sul Web e non sono stato in grado di individuare un hotfix per risolvere il problema. Inoltre, potrebbe trattarsi di un problema di Delphi, dal momento che un programma con funzionalità simili in C# non sembra avere questa perdita. Quest'ultimo fatto mi ha portato a credere che potrebbe esserci un altro, migliore, modo di ottenere le informazioni di cui ho bisogno in Delphi senza ottenere una perdita di memoria.

Ho incluso la fonte in un piccolo programma per esporre la perdita di memoria di seguito. Se viene eseguita la riga sObject.Path_ sotto il commento { Leak! }, si verifica la perdita di memoria. Se commento, non c'è nessuna perdita. (. Ovviamente, nel programma "vero", io faccio qualcosa di utile con il risultato della chiamata :) sObject.Path_ metodo)

Con un po 'veloce 'n sporco Task Manager di Windows profiling sulla mia macchina, ho trovato il seguente:

 
         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 

Credo che la mia domanda sia: qualcun altro ha riscontrato questo problema? Se è così, è davvero un problema di Windows, e c'è una correzione? O (più probabilmente) è il mio codice Delphi rotto, e c'è un modo migliore per ottenere le informazioni che mi servono?

Noterete che in diverse occasioni, nil viene assegnato a oggetti, contrariamente allo spirito Delphi ... Questi sono oggetti COM che non ereditano da TObject e non hanno alcun distruttore che possa chiamare. Assegnando loro nil, il garbage collector di Windows li pulisce.

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. 

risposta

7

Posso riprodurre il comportamento, il codice perde memoria su Windows XP 64 e non su Windows XP. È interessante notare che ciò si verifica solo se la proprietà Path_ viene letta, sta leggendo Properties_ o Security_ con lo stesso codice non viene rilevata alcuna perdita di memoria. Un problema specifico della versione di Windows in WMI sembra la causa più probabile di ciò. Il mio sistema è aggiornato AFAIK, quindi non c'è probabilmente neanche una correzione per questo.

Desidero commentare il ripristino di tutte le varianti e le variabili di interfaccia, tuttavia. Si scrive

Si noterà in diverse occasioni, nullo viene assegnato agli oggetti, contrarie allo spirito Delphi ... Questi sono oggetti COM che non ereditano da TObject, e non hanno alcuna distruttore posso chiamare. Assegnando zero a loro, il garbage collector di Windows li pulisce.

questo non è vero, e di conseguenza non v'è alcuna necessità di impostare le variabili per nil e Unassigned. Windows non ha un garbage collector, quello con cui si ha a che fare sono oggetti conteggiati di riferimento, che vengono immediatamente distrutti quando il conteggio dei riferimenti raggiunge 0. Il compilatore Delphi inserisce le chiamate necessarie per incrementare e decrementare il conteggio dei riferimenti secondo necessità.Le assegnazioni di nil e Unassigned decremento del conteggio di riferimento, e libera l'oggetto quando raggiunge 0.

Una nuova assegnazione ad una variabile, o della fuoriuscita della procedura prendersi cura di questo e, quindi assegnazioni aggiuntivi sono (sebbene non sbagliato) superfluo e diminuire la chiarezza del codice. Il seguente codice è completamente equivalente e non perde alcuna memoria aggiuntiva:

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; 

direi che si dovrebbe resettare esplicitamente le interfacce solo se questa realtà non liberare l'oggetto (in modo che il conteggio ref corrente deve essere 1) e la distruzione stessa dovrebbe davvero accadere esattamente a questo punto. Esempi per questi ultimi sono che una grande porzione di memoria può essere liberata, o che un file deve essere chiuso o un oggetto di sincronizzazione da rilasciare.

+0

Probabilmente hai ragione, ma il ripristino esplicito delle variabili ha risolto un'altra perdita di memoria. Forse sono andato un po 'fuori bordo, resettando assolutamente tutto, ma hey, le perdite di memoria non erano ancora finite :). Grazie per aver riprodotto l'errore e segnalato su di esso, però! – jqno

0

si dovrebbe memorizzare il valore di ritorno di

sObject.Path_; 

in una variabile e renderlo SWbemObjectPath. Questo è necessario per rendere corretto il conteggio dei riferimenti.

+0

Grazie per la risposta! Sfortunatamente, non ha funzionato. Ho dichiarato un 'var Path: SWbemObjectPath;' e assegnato il valore di ritorno di 'sObject.Path_' ad esso. L'impronta della memoria rimane la stessa, indipendentemente dal fatto che io abbia perso o meno la variabile Path. – jqno

+0

Non è vero, la gestione del conteggio dei riferimenti non richiede l'assegnazione a una variabile, funziona altrettanto bene senza di essa. A meno che il compilatore non lo ottimizzi comunque, questo aggiungerà solo un'altra coppia di _AddRef() e _Release(). – mghie