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.