delphi delphi-2007

delphi - La clase de instancia de parches requiere que la clase base esté en la misma unidad?



delphi-2007 (1)

Estoy usando la siguiente función para parchear una clase de instancia de un objeto existente. La razón es que necesito parchear una función protegida de una clase de terceros.

procedure PatchInstanceClass(Instance: TObject; NewClass: TClass); type PClass = ^TClass; begin if Assigned(Instance) and Assigned(NewClass) and NewClass.InheritsFrom(Instance.ClassType) and (NewClass.InstanceSize = Instance.InstanceSize) then begin PClass(Instance)^ := NewClass; end; end;

Pero por alguna razón, el código solo funciona si la clase base está definida en mi propia unidad. ¿Porque eso? ¿Hay una solución para que funcione sin él?

Esto no funciona

unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, wwdblook, Wwdbdlg; type TwwDBLookupComboDlg = class(Wwdbdlg.TwwDBLookupComboDlg); // This is necessary TForm1 = class(TForm) Button1: TButton; wwDBLookupComboDlg1: TwwDBLookupComboDlg; procedure FormCreate(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} type TButtonEx = class(TButton) end; TwwDBLookupComboDlgEx = class(TwwDBLookupComboDlg) end; procedure PatchInstanceClass(Instance: TObject; NewClass: TClass); type PClass = ^TClass; begin if Assigned(Instance) and Assigned(NewClass) and NewClass.InheritsFrom(Instance.ClassType) and (NewClass.InstanceSize = Instance.InstanceSize) then begin PClass(Instance)^ := NewClass; end; end; procedure TForm1.FormCreate(Sender: TObject); begin PatchInstanceClass(Button1, TButtonEx); showmessage(Button1.ClassName); // Good: TButtonEx PatchInstanceClass(wwDBLookupComboDlg1, TwwDBLookupComboDlgEx); showmessage(wwDBLookupComboDlg1.ClassName); // Bad: TwwDBLookupComboDlg (should be TwwDBLookupComboDlgEx) end; end.

Esto funciona (la única diferencia es la redefinición de TwwDBLookupComboDlg)

type TwwDBLookupComboDlg = class(wwdbdlg.TwwDBLookupComboDlg); // <------ added! procedure TForm1.FormCreate(Sender: TObject); begin PatchInstanceClass(wwDBLookupComboDlg1, TwwDBLookupComboDlgEx); showmessage(wwDBLookupComboDlg1.ClassName); // shows TwwDBLookupComboDlgEx :-) end; end.

Mientras trabajaba en ese ejemplo, descubrí que este fenómeno solo ocurre con TwwDBLookupComboDlg, pero no con TButton. No se por que Desafortunadamente, wwdbdlg.pas no es gratis.

Actualizar:

Descubrí: si comparo TButton y TButtonEx , ambos valores son 608.

Si comparo wwdlg.TwwDBLookupComboDlg y TwwDBLookupComboDlgEx , los tamaños son 940 y 944.

Si comparo Unit1.TwwDBLookupComboDlg y TwwDBLookupComboDlgEx , los tamaños son 944 y 944.

Entonces ... el problema real es: si defino TwwDBLookupComboDlg = class(Wwdbdlg.TwwDBLookupComboDlg); , el tamaño de la instancia crece en 4 bytes!

Una simple demostración. Este programa:

{$APPTYPE CONSOLE} uses Dialogs; type TOpenDialog = class(Vcl.Dialogs.TOpenDialog); TOpenDialogEx = class(TOpenDialog); begin Writeln(Vcl.Dialogs.TOpenDialog.InstanceSize); Writeln(TOpenDialog.InstanceSize); Writeln(TOpenDialogEx.InstanceSize); Readln; end.

emite

188 192 192

cuando se compila con Delphi 2007. Sin embargo, con XE7 la salida es:

220 220 220

Si bien este problema ocurre en TOpenDialog , no ocurre con TCommonDialog .

Actualización 2: ejemplo mínimo

program Project1; {$APPTYPE CONSOLE} uses Classes, Dialogs; type TOpenDialog = class(TCommonDialog) private FOptionsEx: TOpenOptionsEx; end; TOpenDialogEx = class(Project1.TOpenDialog); begin Writeln(Project1.TOpenDialog.InstanceSize); // 100 Writeln(TOpenDialogEx.InstanceSize); // 104 Readln; end.


Esto parece ser una rareza (quizás un error) en el comportamiento del compilador para versiones anteriores del compilador. He reducido esto al siguiente código:

{$APPTYPE CONSOLE} type TClass1 = class FValue1: Double; FValue2: Integer; end; TClass2 = class(TClass1); begin Writeln(TClass1.InstanceSize); Writeln(TClass2.InstanceSize); Writeln; Writeln(Integer(@TClass1(nil).FValue1)); Writeln(Integer(@TClass1(nil).FValue2)); Writeln; Writeln(Integer(@TClass2(nil).FValue1)); Writeln(Integer(@TClass2(nil).FValue2)); Readln; end.

En Delphi 6 la salida es:

20 24 8 16 8 16

El compilador parece manejar la alineación de manera diferente para las dos declaraciones de clase. La clase contiene un doble que tiene una alineación de 8 bytes, seguido de un entero de 4 bytes. Así que la clase realmente debería tener 4 bytes de relleno al final para que su tamaño sea un múltiplo de 8. La primera clase no tiene este relleno, la segunda sí.

El código aquí demuestra que las compensaciones de los campos no han cambiado, y la diferencia está en el relleno al final del tipo que existe para lograr la alineación.

Obviamente, no va a obtener un parche para el compilador de Delphi 2007. Mi sospecha es que puede eliminar la verificación de que NewClass.InstanceSize = Instance.InstanceSize y su código de parche se comportarán correctamente. Entonces la responsabilidad recae en usted para garantizar que no agregue ningún miembro de datos a su clase de parches.

Otro enfoque podría ser utilizar un mecanismo diferente para parchear el código. Sin un mayor conocimiento del problema original, es difícil para mí decir lo que podría ser.