2009-06-25 13 views
5

Tengo una pérdida de memoria cuando uso WMI desde Delphi 7 para consultar una pc (remota). La pérdida de memoria solo ocurre en Windows 2003 (y Windows XP 64). Windows 2000 está bien, al igual que Windows 2008. Me pregunto si alguien ha experimentado un problema similar.Fuga de memoria con WMI en Delphi 7

El hecho de que la filtración solo ocurra en ciertas versiones de Windows implica que podría tratarse de un problema de Windows, pero he estado buscando en la web y no he podido encontrar una revisión para resolver el problema. Además, podría ser un problema de Delphi, ya que un programa con una funcionalidad similar en C# no parece tener esta fuga. El último hecho me ha llevado a creer que podría haber otra forma mejor de obtener la información que necesito en Delphi sin tener una fuga de memoria.

He incluido la fuente en un programa pequeño para exponer la fuga de memoria a continuación. Si se ejecuta la línea sObject.Path_ debajo del comentario { Leak! }, se produce la pérdida de memoria. Si lo comento, no hay filtración. (Obviamente, en el programa "real", hago algo útil con el resultado de la llamada al método sObject.Path_ :).)

Con un poco de rápido y sucio perfil del Administrador de tareas de Windows en mi máquina, encontré lo siguiente:

 
         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 

Supongo que mi pregunta es: ¿Alguien más ha encontrado este problema? Si es así, ¿es de hecho un problema de Windows y hay una revisión? O (más probable) es mi código Delphi roto, y ¿hay una mejor manera de obtener la información que necesito?

Notarás en varias ocasiones que nil se asigna a objetos, contrariamente al espíritu Delphi ... Estos son objetos COM que no heredan de TObject, y no tienen un destructor al que pueda llamar. Al asignarles nil, el recolector de basura de Windows los limpia.

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. 

Respuesta

7

Puedo reproducir el comportamiento, el código pierde memoria en Windows XP 64 y no en Windows XP. Curiosamente, esto ocurre solo si se lee la propiedad Path_, al leer Properties_ o Security_ con el mismo código no se pierde ninguna memoria. Un problema específico de la versión de Windows en WMI parece ser la causa más probable de esto. Mi sistema está actualizado AFAIK, por lo que probablemente tampoco hay una revisión para esto.

Sin embargo, me gustaría comentar sobre el restablecimiento de todas las variantes y variables de interfaz. Usted escribe

Se dará cuenta en varias ocasiones, nada se le asigna a los objetos, contrariamente al espíritu Delphi ... Estos son los objetos COM que no heredan de TObject, y no tienen destructor puedo llamar. Al asignarles nil, el recolector de basura de Windows los limpia.

esto no es cierto, y por lo tanto no hay necesidad de ajustar las variables de nil y Unassigned. Windows no tiene un recolector de basura, con lo que se trata son objetos contados por referencia, que se destruyen inmediatamente una vez que el recuento de referencias llega a 0. El compilador Delphi inserta las llamadas necesarias para incrementar y disminuir el recuento de referencias según sea necesario.Sus asignaciones a nil y Unassigned decremento del contador de referencia, y libre del objeto cuando se llega a 0.

Una nueva asignación a una variable, o la excitante del procedimiento hacerse cargo de esto y, por lo asignaciones adicionales (aunque no equivocado) superfluo y disminuir la claridad del código. El siguiente código es completamente equivalente y no se escape ninguna memoria adicional:

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; 

yo diría que hay que restablecer de forma explícita las interfaces sólo si esto realmente libre el objeto (por lo que el recuento ref actual tiene que ser 1) y la destrucción misma realmente debería ocurrir exactamente en este punto. Ejemplos de esto último son que se puede liberar una gran cantidad de memoria o que se debe cerrar un archivo o liberar un objeto de sincronización.

+0

Probablemente tenga razón, pero al restablecer explícitamente las variables se resolvió otra pérdida de memoria. Tal vez fui un poco por la borda al restablecer absolutamente todo, pero bueno, las pérdidas de memoria no habían desaparecido todavía :). ¡Gracias por reproducir el error y reportarlo! – jqno

0

debe almacenar el valor de retorno de

sObject.Path_; 

en una variable y que sea SWbemObjectPath. Esto es necesario para hacer que el recuento de referencias sea correcto.

+0

Gracias por su respuesta! Desafortunadamente, no funcionó. Decidí una 'var Path: SWbemObjectPath;' y le asigné el valor de retorno de 'sObject.Path_'. La huella de memoria sigue siendo la misma, ya sea que no tenga la variable Path o no. – jqno

+0

No es cierto, la gestión del recuento de referencias no requiere la asignación a una variable, funciona igual de bien sin ella. A menos que el compilador lo optimice de todos modos, solo agregará otro par de _AddRef() y _Release(). – mghie