delphi interface parameters const

delphi - ¿Debería el compilador insinuar/advertir al pasar instancias de objeto directamente como parámetros de interfaz const?



interface parameters (3)

Es un error. La conversión de la instancia a la referencia de la interfaz en RunLeakCrash debe ser una variable temporal, manteniéndola activa mientras dure RunLeakCrash.

¿El compilador debería advertir / advertir al pasar una nueva instancia de un objeto a un método que tenga un parámetro de interfaz const de una interfaz que implemente la clase del objeto?

Edición : la muestra por supuesto es simple para ilustrar el problema. Pero en la vida real se vuelve mucho más complejo: ¿y si la creación y el uso están en un código que está muy alejado (unidades diferentes, clases diferentes, proyectos diferentes)? ¿Qué pasa si es mantenido por diferentes personas? ¿Qué pasa si un parámetro no const se convierte en uno const y no se puede verificar todo el código de llamada (porque la persona que cambia el código no tiene acceso a todos los códigos de llamada)?

Codifique los siguientes bloqueos, y es muy difícil encontrar la causa.

Primero el registro:

1.Run begin 1.RunLeakCrash 2.RunLeakCrash begin NewInstance 1 AfterConstruction 0 3.LeakCrash begin _AddRef 1 4.Dump begin 4.Dump Reference=10394576 4.Dump end _Release 0 _Release Destroy BeforeDestruction 0 3.LeakCrash Reference got destroyed if it had a RefCount of 1 upon entry, so now it can be unsafe to access it _AddRef 1 4.Dump begin 4.Dump Reference=10394576 4.Dump end _Release 0 _Release Destroy BeforeDestruction 0 3.LeakCrash end with exception 1.Run end EInvalidPointer: Invalid pointer operation

Luego, el código que libera prematuramente la instancia del objeto implementando una interfaz:

//{$define all} program InterfaceConstParmetersAndPrematureFreeingProject; {$APPTYPE CONSOLE} uses SysUtils, Windows, MyInterfacedObjectUnit in ''../src/MyInterfacedObjectUnit.pas''; procedure Dump(Reference: IInterface); begin Writeln('' 4.Dump begin''); Writeln('' 4.Dump Reference='', Integer(PChar(Reference))); Writeln('' 4.Dump end''); end; procedure LeakCrash(const Reference: IInterface); begin Writeln('' 3.LeakCrash begin''); try Dump(Reference); // now we leak because the caller does not keep a reference to us Writeln('' 3.LeakCrash Reference got destroyed if it had a RefCount of 1 upon entry, so now it can be unsafe to access it''); Dump(Reference); // we might crash here except begin Writeln('' 3.LeakCrash end with exception''); raise; end; end; Writeln('' 3.LeakCrash end''); end; procedure RunLeakCrash; begin Writeln('' 2.RunLeakCrash begin''); LeakCrash(TMyInterfacedObject.Create()); Writeln('' 2.RunLeakCrash end''); end; procedure Run(); begin try Writeln(''1.Run begin''); Writeln(''''); Writeln(''1.RunLeakCrash''); RunLeakCrash(); finally Writeln(''''); Writeln(''1.Run end''); end; end; begin try Run(); except on E: Exception do Writeln(E.ClassName, '': '', E.Message); end; Readln; end.

EInvalidPointer se manifestará dentro de la segunda llamada a Dump(Reference); . El motivo es que el recuento de referencias del objeto subyacente que expone la referencia ya es cero, por lo que el objeto subyacente ya se destruyó.

Algunas notas sobre el código de conteo de referencia insertado u omitido por el compilador:

  • parámetros no marcados con const (como en el procedure Dump(Reference: IInterface); ) obtener implícito los bloques try / finally para realizar el recuento de referencias.
  • los parámetros marcados con const (como en el procedure LeakCrash(const Reference: IInterface); ) no obtienen ningún código de conteo de referencia
  • pasar el resultado de una creación de instancia de objeto (como LeakCrash(TMyInterfacedObject.Create()); ) no genera ningún código de recuento de referencia

Solo todos los comportamientos del compilador anteriores son muy lógicos, pero combinados pueden causar un EInvalidPointer.
EInvalidPointer se manifiesta solo en un patrón de uso muy estrecho.
El patrón es fácil de reconocer por el compilador, pero es muy difícil depurarlo o encontrar la causa cuando lo atrapó.
La solución es bastante simple: almacena en caché el resultado de TMyInterfacedObject.Create() en una variable intermedia, y luego lo pasa a LeakCrash() .

¿El compilador debería advertirle sobre este patrón de uso?

Finalmente, el código que utilicé para rastrear todas las llamadas a _AddRef / _Release / etccetera:

unit MyInterfacedObjectUnit; interface type // Adpoted copy of TInterfacedObject for debugging TMyInterfacedObject = class(TObject, IInterface) protected FRefCount: Integer; function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; public procedure AfterConstruction; override; procedure BeforeDestruction; override; class function NewInstance: TObject; override; property RefCount: Integer read FRefCount; end; implementation uses Windows; procedure TMyInterfacedObject.AfterConstruction; begin InterlockedDecrement(FRefCount); Writeln('' AfterConstruction '', FRefCount); end; procedure TMyInterfacedObject.BeforeDestruction; begin Writeln('' BeforeDestruction '', FRefCount); if RefCount <> 0 then System.Error(reInvalidPtr); end; class function TMyInterfacedObject.NewInstance: TObject; begin Result := inherited NewInstance; TMyInterfacedObject(Result).FRefCount := 1; Writeln('' NewInstance '', TMyInterfacedObject(Result).FRefCount); end; function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult; begin Writeln('' QueryInterface '', FRefCount); if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE; end; function TMyInterfacedObject._AddRef: Integer; begin Result := InterlockedIncrement(FRefCount); Writeln('' _AddRef '', FRefCount); end; function TMyInterfacedObject._Release: Integer; begin Result := InterlockedDecrement(FRefCount); Writeln('' _Release '', FRefCount); if Result = 0 then begin Writeln('' _Release Destroy''); Destroy; end; end; end.

--jeroen


Yo votaría por una advertencia, ya que incluso los desarrolladores experimentados podrían caer en esta trampa. Si alguien no quiere esa advertencia, se puede desactivar fácilmente, lo que significa que no hay cambios con respecto al comportamiento actual. Es algo así como advertencias a variables no inicializadas o algo así.

Otra solución a este problema podría ser un Assert(InterfaceParameter.RefCount > 0); implícito Assert(InterfaceParameter.RefCount > 0); para los parámetros de interfaz const. Probablemente solo se emita cuando las aserciones están activadas.


pasar el resultado de una creación de instancia de objeto (como LeakCrash (TMyInterfacedObject.Create ());) no genera ningún código de recuento de referencia

Arriba está el error del compilador. Debe crear una var oculta y disminuir el contador cuando el procedimiento existió