原因是我需要修补第三方类的受保护的功能。
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.
这个工作(唯一的区别是重新定义了TwwDBookupComboDlg)
type TwwDBLookupComboDlg = class(wwdbdlg.TwwDBLookupComboDlg); // <------ added! procedure TForm1.FormCreate(Sender: TObject); begin PatchInstanceClass(wwDBLookupComboDlg1,TwwDBLookupComboDlgEx); showmessage(wwDBLookupComboDlg1.ClassName); // shows TwwDBLookupComboDlgEx :-) end; end.
在这个例子中,我发现这种现象只发生在TwwDBookupComboDlg,但不是与TButton。我不知道为什么不幸的是,wwdbdlg.pas不是免费的。
更新:
我发现:如果我比较TButton和TButtonEx,这两个值都是608。
如果我比较wwdlg.TwwDBLookupComboDlg和TwwDBookupComboDlgEx,那么大小是940和944。
如果我比较Unit1.TwwDBLookupComboDlg和TwwDBookupComboDlgEx,那么大小是944和944。
所以…实际的问题是:如果我定义了TwwDBookupComboDlg = 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.
解决方法
{$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
编译器似乎对于两个类声明来处理对齐方式不同。该类包含一个双字节,具有8字节对齐,后面是一个4字节的整数。所以类最终应该有4个字节的填充,使其大小为8的倍数。第一个类没有这个填充,第二个类没有这个填充。
这里的代码证明了对字段的偏移量没有改变,并且差异仅在于存在以实现对齐的类型的结尾处的填充。
显然你不会得到Delphi 2007编译器的补丁。我的怀疑是,您可以删除检查NewClass.InstanceSize = Instance.InstanceSize和您的修补程序代码仍将行为正确。然后,您的职责是确保您不要在修补课中添加任何数据成员。