delphi - 修补实例类需要基类位于同一单元吗?

标签 delphi delphi-2007

我正在使用以下函数来修补现有对象的实例类。 原因是我需要修补第三方类的 protected 函数。

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 不是免费的。

<小时/>

更新:

我发现:如果我比较 TButtonTButtonEx,两个值都是 608。

如果我比较 wwdlg.TwwDBLookupComboDlgTwwDBLookupComboDlgEx,则大小为 940 和 944。

如果我比较 Unit1.TwwDBLookupComboDlgTwwDBLookupComboDlgEx,则大小为 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

when compiled with Delphi 2007. However, with XE7 the output is:

220
220
220

While this issue occurs on TOpenDialog, it does not happen with TCommonDialog.

Update 2: Minimal example

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 字节对齐方式的 double 值,后跟一个 4 字节整数。因此,该类确实应该在末尾有 4 个字节的填充,以使其大小成为 8 的倍数。第一个类没有此填充,第二个类有。

这里的代码证明了字段的偏移量没有改变,区别只是为了实现对齐而存在的类型末尾的填充。

显然您不会获得 Delphi 2007 编译器的补丁。我怀疑您可以删除 NewClass.InstanceSize = Instance.InstanceSize 的检查,并且您的修补代码仍将正常运行。那么您有责任确保不向修补类添加任何数据成员。

另一种方法可能是使用不同的机制来修补代码。如果不了解原始问题的更多信息,我很难说出那可能是什么。

关于delphi - 修补实例类需要基类位于同一单元吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41181767/

相关文章:

delphi - 如何下载 Unicode 文件并将其加载到 TTreeView 中?

multithreading - Delphi如何使用线程

delphi - 要安装哪些更新才能达到最新版本的 Delphi 2007?

使用 Windows 成像组件 (WIC) 的 Delphi 2007

delphi - 作为事件处理程序的接口(interface)方法

delphi - 如何在虚拟模式下自动调整 ListView 的列宽?

delphi - 我需要 Delphi 的组件来创建一个可以通过皮肤美化的 Web 应用程序

delphi - 全局范围的程序不能通用吗?此限制有技术原因吗?

delphi - 为什么释放内存后我的程序的内存使用量没有恢复正常?

delphi - 在 Delphi 中,我的 DLL 中是否必须分配函数的返回 pchar