Класс экземпляра экземпляра требует, чтобы базовый класс находился в одной единице?
Я использую следующую функцию для исправления класса экземпляра существующего объекта.
Причина в том, что мне нужно исправить защищенную функцию стороннего класса.
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;
Но по какой-то причине код работает только в том случае, если базовый класс определен в моем собственном блоке.
Почему так? Есть ли работа, чтобы заставить ее работать без нее?
Это не работает
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.
Это работает (единственное различие заключается в повторном определении TwwDBLookupComboDlg)
type
TwwDBLookupComboDlg = class(wwdbdlg.TwwDBLookupComboDlg); // <------ added!
procedure TForm1.FormCreate(Sender: TObject);
begin
PatchInstanceClass(wwDBLookupComboDlg1, TwwDBLookupComboDlgEx);
showmessage(wwDBLookupComboDlg1.ClassName); // shows TwwDBLookupComboDlgEx :-)
end;
end.
Во время работы над этим примером я узнал, что это явление происходит только с TwwDBLookupComboDlg, но не с TButton. Я не знаю почему. К сожалению, wwdbdlg.pas не является бесплатным.
Update:
Я узнал: если я сравниваю TButton
и TButtonEx
, оба значения равны 608.
Если я сравниваю wwdlg.TwwDBLookupComboDlg
и TwwDBLookupComboDlgEx
, то размеры равны 940 и 944.
Если я сравниваю Unit1.TwwDBLookupComboDlg
и TwwDBLookupComboDlgEx
, то размеры равны 944 и 944.
Итак... актуальная проблема: если я определяю TwwDBLookupComboDlg = class(Wwdbdlg.TwwDBLookupComboDlg);
, размер экземпляра увеличивается на 4 байта!
Простая демонстрация. Эта программа:
{$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.
испускает
188
192
192
при компиляции с Delphi 2007. Однако с XE7 вывод:
220
220
220
Пока эта проблема возникает в TOpenDialog
, этого не происходит с TCommonDialog
.
Обновление 2: Минимальный пример
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.
Ответы
Ответ 1
Это выглядит как странность (возможно, ошибка) в поведении компилятора для более старых версий компилятора. Я сократил это до следующего кода:
{$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.
На Delphi 6 вывод:
20
24
8
16
8
16
Компилятор, по-видимому, обрабатывает выравнивание по-разному для двух объявлений класса. Класс содержит double, который имеет 8-байтовое выравнивание, за которым следует целое число в 4 байта. Таким образом, класс действительно должен иметь 4 байта заполнения в конце, чтобы сделать его размер кратным 8. Первый класс не имеет этого дополнения, второй - делает.
В этом коде показано, что смещения в полях не изменились, а разница находится только в дополнении в конце типа, который существует для достижения выравнивания.
Очевидно, вы не получите патч для компилятора Delphi 2007. Мое подозрение в том, что вы можете удалить проверку, что NewClass.InstanceSize = Instance.InstanceSize
и ваш код исправления по-прежнему будет вести себя правильно. Тогда бремя ответственности за то, что вы не добавляете каких-либо членов данных в свой класс исправления.
Другим подходом может быть использование другого механизма для исправления кода. Не зная о первоначальной проблеме, мне трудно сказать, что это может быть.