2012-06-11 17 views
6

Recibo un error inesperado Violación de acceso en el siguiente código:AV extraño cuando se almacena una referencia Delphi interfaz

program Project65; 

{$APPTYPE CONSOLE} 

{$R *.res} 

uses 
    SysUtils; 

type 
    ITest = interface 
    end; 

    TTest = class(TInterfacedObject, ITest) 
    end; 

var 
    p: ^ITest; 

begin 
    GetMem(p, SizeOf(ITest)); 
    p^ := TTest.Create; // AV here 
    try 
    finally 
    p^ := nil; 
    FreeMem(p); 
    end; 
end. 

sé que las interfaces deben ser utilizados de manera diferente. Sin embargo, estoy trabajando en una base de código heredada que utiliza este enfoque. Y estuve muy sorprendido de ver que no es suficiente reservar la memoria SizeOf (ITest) para poner allí un ITest.

Ahora interesante si cambio de la primera línea a

GetMem(p, 21); 

que el AV se ha ido. (20 bytes o menos falla). ¿Cuál es la explicación para esto?

(estoy usando Delphi XE2 Update 4 + Revisión)

Por favor, no hacer comentarios sobre lo horrible que el código es ni sugiere cómo esto podría ser codificado correctamente. En su lugar, responda por qué es necesario reservar 21 bytes en lugar de SizeOf (ITest) = 4?

+0

AV = violación de acceso? – kol

+0

Tu código se ve realmente extraño. ¿Por qué necesita "^ ITest" y el par GetMem/FreeMem? TTest es un descendiente de TInterfacedObject, por lo que p debería ser simplemente un ITest. Se cuenta de referencia, por lo que se destruirá automáticamente cuando se salga del alcance. No es necesario usar GetMem/FreeMem. – kol

+1

Esta es la forma totalmente incorrecta de trabajar con interfaces. ¿Puedes explicar qué es lo que esperas lograr, para que alguien pueda orientarte en una mejor dirección? –

Respuesta

26

lo que ha escrito efectivamente está haciendo la siguiente lógica detrás de las escenas:

var 
    p: ^ITest; 
begin 
    GetMem(p, SizeOf(ITest)); 
    if p^ <> nil then p^._Release; // <-- AV here 
    PInteger(p)^ := ITest(TTest.Create); 
    p^._AddRef; 
    ... 
    if p^ <> nil then p^._Release; 
    PInteger(p)^ := 0; 
    FreeMem(p); 
end; 

GetMem() no está garantizada a cero fuera de lo que se destina. Cuando asigna la nueva instancia de objeto a la interfaz varaiable, si los bytes no son ceros, la RTL pensará que ya hay una referencia de interfaz existente e intentará llamar a su método _Release(), causando el AV ya que no está respaldado por un real instancia de objeto. Es necesario poner a cero los bytes asignados de antemano, entonces la RTL verá una referencia nil interfaz y no tratar de llamar a su método _Release() más:

program Project65; 

{$APPTYPE CONSOLE} 

{$R *.res} 

uses 
    SysUtils; 

type 
    ITest = interface 
    end; 

    TTest = class(TInterfacedObject, ITest) 
    end; 

var    
    p: ^ITest;    

begin    
    GetMem(p, SizeOf(ITest));    
    try 
    FillChar(p^, SizeOf(ITest), #0); // <-- add this! 
    p^ := TTest.Create; // <-- no more AV 
    try 
     ... 
    finally 
     p^ := nil; 
    end; 
    finally 
    FreeMem(p); 
    end; 
end. 
+0

Gracias Remy, bien explicado –

+6

O usa AllocMem en lugar de GetMem + FreeMem –

+0

Corrección: quise decir GetMem + FillChar. El FreeMem todavía es necesario para AllocMem. –

Cuestiones relacionadas