2011-08-07 18 views
5
unit Unit7; 

interface 

uses Classes; 

type 
    TListener = class(TThread) 
    procedure Execute; override; 
    end; 

    TMyClass = class 
    o1,o2: Tobject; 
    procedure FreeMyObject(var obj: TObject); 
    constructor Create; 
    destructor Destroy; override; 
    end; 

implementation 

uses Windows, SysUtils; 

var l: TListener; 
    my: TMyClass; 

procedure TListener.Execute; 
var msg:TMsg; 
begin 
    while(GetMessage(msg, Cardinal(-1), 0, 0)) do 
    if(msg.message=6) then begin 
     TMyClass(msg.wParam).FreeMyObject(TObject(msg.lParam)); 
     Exit; 
    end; 
end; 

constructor TMyClass.Create; 
begin 
    inherited; 
    o1:=TObject.Create; 
    o2:=Tobject.Create; // Invalid pointer operation => mem leak 
end; 

destructor TMyClass.Destroy; 
begin 
    if(Assigned(o1)) then o1.Free; 
    if(Assigned(o2)) then o2.Free; 
    inherited; 
end; 

procedure TMyClass.FreeMyObject(var obj: TObject); 
begin 
    FreeAndNil(obj); 
end; 

initialization 
    l:= TListener.Create(); 
    my:=TMyClass.Create; 

    sleep(1000); //make sure the message loop is set 
    PostThreadMessage(l.ThreadID, 6, Integer(my), Integer(my.o2)); 
finalization 
    l.Free; 
    my.Free; 
end. 

Utilicé el controlador de mensajes para ilustrar mi problema tal como está, para que lo entienda. El diseño real es mucho más complicado. La función 'FreeMyObject' en realidad libera Y crea una instancia usando un paradigma de polimorfismo, pero esto no es necesario. Solo quiero señalar que el diseño debe permanecer igual.¿Por qué hay una pérdida de memoria y cómo solucionarlo?

Ahora la pregunta y el problema: ¿por qué sucede Y cómo solucionarlo? Parece 'si Assigned (o2)' no cabe.

Lo que pienso: Enviar un puntero a my.o2 sería gratis y nulo o2 y trato de hacerlo, pero no pude convertir de puntero a objeto en el manejador de mensajes, no tengo idea de por qué.

¿Alguien podría ayudarlo? Gracias

+0

¿El 'operación puntero no válido => mem leak' pertenece realmente en la línea' o2: = Tobject.Create; ' – mjn

+0

@mjn. Probablemente pertenece a la línea correspondiente en el destructor. :) – GolezTrol

+0

Será mejor que use valores superiores a WM_APP para los números de mensaje. 6 es WM_ACTIVATE y puede causar problemas. – GolezTrol

Respuesta

6

Usted libre o2 dos veces. Una vez como resultado del mensaje y una vez del destructor.

Cree que está configurando o2 en nil cuando llama al FreeMyObject pero no lo está. De hecho, está configurando msg.lParam en 0.

o2 es una variable que presenta una referencia a un objeto. Está pasando el valor de o2 y cuando pasa por valor no puede modificar la variable cuyo valor ha pasado. Por lo tanto, debe pasar una referencia al o2. Para ello es necesario añadir un nivel adicional de redirección y pasar un puntero a o2, así:

if(msg.message=6) then begin 
    FreeAndNil(PObject(msg.lParam)^); 
    Exit; 
end; 

... 

PostThreadMessage(l.ThreadID, 6, 0, LPARAM(@my.o2)); 

No es necesario FreeMyObject, sólo se puede llamar directamente FreeAndNil. Y no necesita pasar una instancia en el mensaje.

¡Espero que su código real no sea tan extraño como este! ;-)

+1

El modelo recomendado en PostThreadMessage que está al revés y a prueba de futuro (64 bit) es PostThreadMessage (l.ThreadID, 6,0, LParam (@ my.o2)); –

+0

@LU RD Gracias, tienes toda la razón, lo actualizaré, ¡debería saberlo ya que mi propio código dice eso hace un par de meses! –

+0

Sí, ya lo probé y falló. El problema es mucho en otro lugar. Deje B = clase (A). En A, este mensaje se envía donde lparam = @ Self - debería funcionar. Pero no lo hará. @Self no señalará la dirección donde se ubica la referencia a A. Tristemente, perdí 2 días hasta que lo encontré. ¡Gracias por el esfuerzo aunque aceptado! – netboy

1

Esto es lo que está pasando: comienza

Programa. La inicialización se ejecuta y envía un mensaje al hilo, que llama al FreeAndNil en la referencia que se transfiere. Esto establece la referencia que se transfiere a nil, pero no establece el campo de objeto que contiene o2 en nil. Esa es una referencia diferente.

Luego, en el destructor, dado que el campo no es nil, intenta liberarlo nuevamente y se obtiene un error doblemente libre (excepción de operación del puntero no válida). Desde que generó una excepción en el destructor, el TMyClass nunca se destruye y usted obtiene una pérdida de memoria.

Si desea hacerlo bien, pase un identificador de algún tipo a FreeMyObject en lugar de una referencia. Como un número entero 2, o una cadena o2. Luego, utilice FreeMyObject para usar este valor para buscar lo que debería estar llamando al FreeAndNil. (Si tiene Delphi 2010 o posterior, eso es bastante fácil de hacer con RTTI.) Es un poco más de trabajo, pero solucionará los errores que está viendo.

+0

RTTI está por encima, solo necesita un puntero a la variable. –

+0

@David: El potencial de violación de la encapsulación en su respuesta me asusta ...: P –

+0

no es un código real, ¿o sí? ¿Cómo pasa una cadena, que no funciona de todos modos, ya que no se organiza, y el uso de la clase rtti como una gran encapsulación? –

3

Si desea FreeAndNil un objeto que envíe solo el objeto de referencia Integer(my.o2) no es suficiente - necesita Integer(@my.o2). También debe hacer los cambios correspondientes en su código.

Debido a que su código es difícil de depurar He escrito una demostración sencilla para dar una idea de los cambios de código necesarios:

type 
    PObject = ^TObject; 

procedure FreeObj(PObj: PObject); 
var 
    Temp: TObject; 

begin 
    Temp:= PObj^; 
    PObj^:= nil; 
    Temp.Free; 
end; 

procedure TForm17.Button1Click(Sender: TObject); 
var 
    Obj: TList; 
    PObj: PObject; 

begin 
    Obj:= TList.Create; 
    PObj:= @Obj; 
    Assert(Obj <> nil); 
    FreeObj(PObj); 
    Assert(Obj = nil); 
end; 
Cuestiones relacionadas