Delphi: конструкция не вызывает переопределенный виртуальный конструктор

у меня есть пример потомка 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;

За исключением того, что он никогда не вызывает мой конструктор и не вызывает конструктор 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;

Версия в TPicture:

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):

  • Иэн Бойд (я): Неудачно (Windows 7 64-бит)
  • Dale: Fails (Windows 7 64-бит)
  • Дэйв: Неудачно (Windows 7 64-бит)
  • Крис: Неудачно (Windows 7 64-бит)
  • Джейми: Неудачно (Windows 7 64-бит)
  • Джей: Неудачно (Windows XP 32-бит)
  • Сервер сборки клиента: сбой (32-разрядная версия Windows 7)

Вот источник.


person Ian Boyd    schedule 16.03.2011    source источник
comment
Возможно, вам стоит вмешаться в директивы компилятора, некоторые оптимизации могут повлиять на вызовы таблиц виртуальных методов для вашего кода и не повлияют на библиотеку VCL, поскольку она предварительно скомпилирована.   -  person too    schedule 17.03.2011
comment
@too, например, какая директива компилятора влияет на правильное выполнение вызовов таблицы виртуальных методов?   -  person jachguate    schedule 17.03.2011
comment
@Ian, вы уверены, что g1.classtype по-прежнему TMyBitmap непосредственно перед последней строкой вашей подпрограммы Button1Click? Бьюсь об заклад, класс где-то меняется в непоказанных строках. Как @David, я получаю два звуковых сигнала также в Delphi XE, у меня нет D5 для тестирования, но мне кажется маловероятным, что такая ошибка присутствует в то время для меня (даже в D1). :)   -  person jachguate    schedule 17.03.2011
comment
@jachguate Я тестировал с Delphi 2010 и Delphi 6. Очевидно, что у @Ian есть дополнительный код, который мы не видим.   -  person David Heffernan    schedule 17.03.2011
comment
@ Дэвид Хеффернан. Я обновил вопрос, добавив снимок экрана с моим кодом   -  person Ian Boyd    schedule 17.03.2011
comment
@Ian Извините, но я не собираюсь вводить ваш код со скриншота !!   -  person David Heffernan    schedule 17.03.2011
comment
@jachguate, я обновил вопрос до версии кода, который выполняет ODS имен классов. я уверен, что g1.ClassType все еще TMyBitmap прямо перед последней строкой моего обработчика событий.   -  person Ian Boyd    schedule 17.03.2011
comment
Это действительно очень длинный вопрос!   -  person Andreas Rejbrand    schedule 17.03.2011
comment
@David: Естественно, вы должны были распознавать растровое изображение!   -  person Andreas Rejbrand    schedule 17.03.2011
comment
@Andreas Rejbrand, я просто хотел держать всех читателей в курсе того, что я пробовал, или свидетельств, которые я собрал. (т.е. не так, как будто он создал вопрос и сдался).   -  person Ian Boyd    schedule 17.03.2011
comment
@Ian Здорово, что проблема наконец решена. Я знал, что это должно быть что-то, чего остальные из нас не увидят, но я не думаю, что когда-либо смог бы решить это без доступа к исходному тексту D5. Я никогда не сомневался, что у тебя проблема, кстати, я просто не мог сообразить, в чем она может быть. Я удалил свой ответ, потому что он больше никому не нужен.   -  person David Heffernan    schedule 17.03.2011


Ответы (2)


Похоже, это проблема области видимости (следующее из D5 Graphics.pas):

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

TGraphicClass = class of TGraphic;

У вас нет проблем с заменой Create, и у вас нет проблем, когда TGraphicClass(Value.ClassType).Create; вызывается из модуля Graphics.pas.

Однако в другом подразделении 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;
person Disillusioned    schedule 17.03.2011
comment
Что ж, черт возьми. Вы, сэр, выигрываете +100 интернетов. Это и есть проблема, и именно ее решение. +1 Чрезвычайно полезный ответ, который отвечает на точный вопрос. Я использовал хорошо известное решение Cracker. Я предполагаю, что в более поздних версиях Delphi конструктор TGraphic был повышен до public, поэтому никто не смог его воспроизвести? - person Ian Boyd; 17.03.2011

Для чего это стоит: я загрузил ваш исходный код (файл ZIP), запустил CannotCloneGraphics.exe и получил сообщение «Недействительно». сообщение об ошибке. Затем я открыл проект (файл DPR) в Delphi 2009, скомпилировал его и запустил. Тогда я не получил никакого сообщения об ошибке, и пользовательский конструктор запустился четыре раза, как и должен.

Таким образом, может показаться, что это проблема ваших установок Delphi 5. Действительно, все ваши машины имели Delphi 5 (время обновляться ?!). Либо есть какая-то проблема с Delphi 5, либо все ваши машины были "подделаны" таким же образом.

Я почти уверен, что у меня где-то есть старый Delphi 4 Personal. Я мог бы установить его и посмотреть, что там происходит ...

Обновлять

Я только что установил Delphi 4 Standard в виртуальной системе Windows 95. Я пробовал этот код:

  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), в конце концов. (Извини, Дэвид!)

person Andreas Rejbrand    schedule 17.03.2011
comment
Наконец, кто-то еще с проблемой. Я согласен, что пора обновиться, но я не отвечаю за это политическое решение. Это делает ответ проблемой с Delphi 5 (и более). Я оцениваю этот ответ как +1 - очень полезным. Мне нужно будет посмотреть, действительно ли ответ Крейга работает с ошибкой. - person Ian Boyd; 17.03.2011
comment
Тебе не нужно извиняться передо мной. Я никогда не сомневался в том, что говорил Ян, я просто не мог воспроизвести это или представить, что означало, что он видел другое поведение. В комментариях вы увидите, что я упомянул тот факт, что никто другой не пробовал использовать D5, и это действительно была проблема. - person David Heffernan; 17.03.2011