delphi – 修补实例类要求基类在同一单元中?

前端之家收集整理的这篇文章主要介绍了delphi – 修补实例类要求基类在同一单元中?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我正在使用以下函数来修补现有对象的实例类。
原因是我需要修补第三方类的受保护的功能
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和您的修补程序代码仍将行为正确。然后,您的职责是确保您不要在修补课中添加任何数据成员。

另一种方法可能是使用不同的机制来修补代码。没有更多关于原始问题的知识,我很难说出可能是什么。

猜你在找的Delphi相关文章