【问题标题】:Delphi: Construction not calling overridden virtual constructorDelphi:构造不调用重写的虚拟构造函数
【发布时间】:2011-07-16 21:59:29
【问题描述】:

我有一个 TBitmap 的后代示例:

TMyBitmap = class(TBitmap)
public
    constructor Create; override;
end;

constructor TMyBitmap.Create;
begin
   inherited;
   Beep;
end;

在运行时,我构造其中一个TMyBitmap 对象,将图像加载到其中,并将其放入表单上的TImage

procedure TForm1.Button1Click(Sender: TObject);
var
   g1: TGraphic;
begin
   g1 := TMyBitmap.Create;
   g1.LoadFromFile('C:\...\example.bmp');

   Image1.Picture.Graphic := g1;
end;

TPicture.SetGraphic 内部,您可以看到它通过构造一个新图形并在新构造的克隆上调用.Assign 来复制图形:

procedure TPicture.SetGraphic(Value: TGraphic);
var
   NewGraphic: TGraphic;
begin
   ...
   NewGraphic := TGraphicClass(Value.ClassType).Create;
   NewGraphic.Assign(Value);
   ...
end;

构造新图形类的行:

NewGraphic := TGraphicClass(Value.ClassType).Create;

正确调用我的构造函数,一切正常。


我想做类似的事情,我想克隆一个TGraphic

procedure TForm1.Button1Click(Sender: TObject);
var
   g1: TGraphic;
   g2: TGraphic;
begin
   g1 := TMyBitmap.Create;
   g1.LoadFromFile('C:\...\example.bmp');

   //Image1.Picture.Graphic := g1;
   g2 := TGraphicClass(g1.ClassType).Create;
end;

除了 this 从不调用我的构造函数,也不会调用 TBitmap 构造函数。它只调用TObject 构造函数。施工后:

g2.ClassName: 'TMyBitmap'
g2.ClassType: TMyBitmap

类型是正确的,但它没有调用我的构造函数,但其​​他地方的代码相同。

为什么?


即使在这个假设的人为示例中,它仍然是一个问题,因为TBitmap 的构造函数没有被调用;内部状态变量未初始化为有效值:

constructor TBitmap.Create;
begin
  inherited Create;
  FTransparentColor := clDefault;
  FImage := TBitmapImage.Create;
  FImage.Reference;
  if DDBsOnly then HandleType := bmDDB;
end;

TPPicture中的版本:

NewGraphic := TGraphicClass(Value.ClassType).Create;

反编译为:

mov eax,[ebp-$08]
call TObject.ClassType
mov dl,$01
call dword ptr [eax+$0c]
mov [ebp-$0c],eax

我的版本:

g2 := TGraphicClass(g1.ClassType).Create;

反编译为:

mov eax,ebx
call TObject.ClassType
mov dl,$01
call TObject.Create
mov ebx,eax

更新一

将“克隆”推送到单独的函数:

function CloneGraphic(Value: TGraphic): TGraphic;
var
    NewGraphic: TGraphic;
begin
   NewGraphic := TGraphicClass(Value.ClassType).Create;
   Result := NewGraphic;
end;

没用。

更新二

显然,我清楚地提供了清晰的屏幕截图,清楚地显示了我的清晰代码,清楚地表明我的清晰代码显然是所有清晰的。很明显:

更新三

这是带有OutputDebugStrings 的明确版本:

{ TMyGraphic }

constructor TMyBitmap.Create;
begin
  inherited Create;
    OutputDebugStringA('Inside TMyBitmap.Create');
end;

function CloneGraphic(Value: TGraphic): TGraphic;
var
    NewGraphic: TGraphic;
begin
    NewGraphic := TGraphicClass(Value.ClassType).Create;
    Result := NewGraphic;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
    g1: TGraphic;
    g2: TGraphic;
begin
    OutputDebugString('Creating g1');
    g1 := TMyBitmap.Create;
    g1.LoadFromFile('C:\Archive\-=Images=-\ChessvDanCheckmateIn38.bmp');
    OutputDebugString(PChar('g1.ClassName: '+g1.ClassName));

    OutputDebugStringA('Assigning g1 to Image.Picture.Graphic');
    Image1.Picture.Graphic := g1;

    OutputDebugString('Creating g2');
    g2 := Graphics.TGraphicClass(g1.ClassType).Create;
    OutputDebugString(PChar('g2.ClassName: '+g2.ClassName));

    OutputDebugString(PChar('Cloning g1 into g2'));
    g2 := CloneGraphic(g1);
    OutputDebugString(PChar('g2.ClassName: '+g2.ClassName));
end;

原始结果:

ODS: Creating g1 Process Project2.exe ($1138)
ODS: Inside TMyBitmap.Create Process Project2.exe ($1138)
ODS: g1.ClassName: TMyBitmap Process Project2.exe ($1138)
ODS: Assigning g1 to Image.Picture.Graphic Process Project2.exe ($1138)
ODS: Inside TMyBitmap.Create Process Project2.exe ($1138)
ODS: Creating g2 Process Project2.exe ($1138)
ODS: g2.ClassName: TMyBitmap Process Project2.exe ($1138)
ODS: Cloning g1 into g2 Process Project2.exe ($1138)
ODS: g2.ClassName: TMyBitmap Process Project2.exe ($1138)
ODS: g1.ClassName: TMyBitmap Process Project2.exe ($1138)

以及格式化结果:

Creating g1
   Inside TMyBitmap.Create
g1.ClassName: TMyBitmap

Assigning g1 to Image.Picture.Graphic
   Inside TMyBitmap.Create

Creating g2
g2.ClassName: TMyBitmap

Cloning g1 into g2
g2.ClassName: TMyBitmap

g1.ClassName: TMyBitmap

更新四

我尝试关闭所有编译器选项:

注意:不要关闭Extended syntax。没有它,您将无法分配函数的Result未声明的标识符结果)。

更新五

按照@David 的建议,我尝试在其他一些机器上编译代码(都是 Delphi 5):

  • Ian Boyd(我):失败(Windows 7 64 位)
  • 戴尔:失败(Windows 7 64 位)
  • 戴夫:失败(Windows 7 64 位)
  • 克里斯:失败(Windows 7 64 位)
  • Jamie:失败(Windows 7 64 位)
  • Jay:失败(Windows XP 32 位)
  • 客户构建服务器:失败(Windows 7 32 位)

Here's the source.

【问题讨论】:

  • 也许你应该篡改编译器指令,一些优化可能会影响代码的虚拟方法表调用,并且不会影响预编译的 VCL 库。
  • @too,例如,哪个编译器指令影响虚拟方法表调用正确发生?
  • @Ian 你确定 g1.classtype 在你的 Button1Click 例程的最后一行之前仍然是 TMyBitmap 吗?我敢打赌,班级在未显示的行中发生了变化。作为@David,我在Delphi XE中也收到了两声哔哔声,我手头没有D5可供测试,但当时我似乎不太可能出现这样的错误(甚至在D1中也没有)。 :)
  • @jachguate 我用 Delphi 2010 和 Delphi 6 进行了测试。显然@Ian 有一些我们看不到的额外代码。
  • @David Heffernan。我已经用我的代码截图更新了问题

标签: delphi constructor virtual delphi-5


【解决方案1】:

值得一提的是:我下载了您的源代码(ZIP 文件),然后运行 ​​CannotCloneGraphics.exe 并得到“无效”。错误信息。然后我在 Delphi 2009 中打开项目(DPR 文件),编译并运行它。然后我没有收到任何错误消息,并且自定义构造函数运行了四次,这是应该的。

因此,这似乎是您的 Delphi 5 安装的问题。事实上,所有你的机器都有 Delphi 5(是时候升级了?!)。要么是 Delphi 5 有问题,要么你的所有机器都被以同样的方式“篡改”过。

我很确定我在某处有一个旧的 Delphi 4 Personal。我可能会安装它,看看那里会发生什么......

更新

我刚刚在一个虚拟的 Windows 95 系统中安装了 Delphi 4 Standard。我试过这段代码:

  TMyBitmap = class(TBitmap)
  public
    constructor Create; override;
  end;

  ...

  constructor TMyBitmap.Create;
  begin
    inherited;
    ShowMessage('Constructor constructing!');
  end;

  ...

  procedure TForm1.Button1Click(Sender: TObject);
  var
    g, g2: TGraphic;
  begin
    g := TMyBitmap.Create;
    g2 := TGraphicClass(g.ClassType).Create;
    g.Free;
    g2.Free;
  end;

并且我只有一个消息框!因此,这毕竟是Delphi 4(和5)的问题。 (对不起,大卫!)

【讨论】:

  • 最后,其他人解决了这个问题。我同意是时候升级了,但我不是负责那个政治决定的人。这使得答案“与 Delphi 5(及更多)有关”。所以我会评价这个答案+1 - 非常很有帮助。我得看看克雷格的回答是否真的能解决这个问题。
  • 你不需要向我道歉。我从不怀疑伊恩在说什么,我只是无法复制或想象这意味着他看到了不同的行为。在 cmets 中,您会看到我指出没有其他人尝试过使用 D5 的事实,而这确实是问题所在。
【解决方案2】:

这似乎是一个范围界定问题(以下来自 D5 Graphics.pas):

TGraphic = class(TPersistent)
...
protected
  constructor Create; virtual;
...
end;

TGraphicClass = class of TGraphic;

覆盖Create 没有任何问题,从Graphics.pas 单元中调用TGraphicClass(Value.ClassType).Create; 时也没有任何问题。

但是,在另一个单元中,TGraphicClass(Value.ClassType).Create; 无权访问 TGraphic 的受保护成员。因此,您最终会调用TObject.Create;(这是非虚拟的)。

可能的解决方案

  • 编辑并重新编译 Graphics.pas
  • 确保您的克隆方法子类在层次结构中较低。 (例如 TBitmap.Create 是公开的)

编辑:附加解决方案

这是获得对类的受保护成员的访问权限的技术的一种变体。
无法保证解决方案的稳健性,但它似乎确实有效。 :)
恐怕您必须自己进行大量测试。

type
  TGraphicCracker = class(TGraphic)
  end;

  TGraphicCrackerClass = class of TGraphicCracker;

procedure TForm1.Button1Click(Sender: TObject);
var
  a: TGraphic;
  b: TGraphic;
begin
  a := TMyBitmap.Create;
  b := TGraphicCrackerClass(a.ClassType).Create;
  b.Free;
  a.Free;
end;

【讨论】:

  • 好吧,地狱。你先生赢得+100互联网。这正是问题所在,也正是解决方案。 +1 非常有帮助的答案,回答了确切的问题。我使用了众所周知的“Cracker”解决方案。我猜是在后来的Delphi版本中,TGraphic的构造函数被提升为public,这就是为什么没有人能够重现它?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-04-09
  • 1970-01-01
  • 2010-09-09
  • 2011-03-30
  • 1970-01-01
  • 2010-10-02
相关资源
最近更新 更多