Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Delphi Новый топик    Ответить
 Создание TEncoding в классовом конструкторе  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11913
Есть такой класс
type
  TTest1 = class
  strict private
    class var
      FEncoding: TEncoding;
      FObj: TObject;
  strict private
    class constructor Create;
    class destructor Destroy;
  public
    class property Encoding: TEncoding read FEncoding;
    class property Obj: TObject read FObj;
  end;

class constructor TTest1.Create;
begin
  FEncoding := TEncoding.GetEncoding(1251);
  FObj := TObject.Create;
end;

class destructor TTest1.Destroy;
begin
  FObj.Free;
  FEncoding.Free;
end;
если сделать такой вызов
procedure TForm1.Button1Click(Sender: TObject);
begin
  if TTest1.Obj <> nil then
    ShowMessage('OK')
  else
    ShowMessage('Fail');
end;
то увидим OK. А если такой
procedure TForm1.Button1Click(Sender: TObject);
begin
  if TTest1.>>>Encoding<< <> nil then
    ShowMessage('OK')
  else
    ShowMessage('Fail');
end;
получим Fail. Т.е. для второго вызова классовый конструктор не вызывается.

Кто объяснит причину бага?

Повторяется на XE3 и 10.3.1
С уважением, Vasilisk
12 июн 20, 13:57    [22149791]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11913
С таким объявлением
class property Encoding: TEncoding read GetEncoding;
работает
12 июн 20, 14:04    [22149799]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11913
Баг завязан на конкретный класс. Если заменить TEncoding на TMBCSEncoding то все работает
12 июн 20, 14:13    [22149805]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
Kazantsev Alexey
Member

Откуда:
Сообщений: 4469
_Vasilisk_,

Баг компилятора. На 10.4 воспроизводится. Можно репортить.
12 июн 20, 14:20    [22149809]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11913
Обошел багу так
type
  TTest = class
  strict private
    type
      TBugFixEncoding = class(TEncoding);
    class var
      FEncoding: TBugFixEncoding;
  strict private
    class constructor Create;
    class destructor Destroy;
  public
    class property Encoding: TBugFixEncoding read FEncoding;
  end;

class constructor TTest.Create;
begin
  FEncoding := TBugFixEncoding(TEncoding.GetEncoding(1251));
end;

class destructor TTest.Destroy;
begin
  FEncoding.Free;
end;
12 июн 20, 14:24    [22149812]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
shalamyansky
Member

Откуда:
Сообщений: 156
Порядок вызовов конструкторов классов неопределен. Все классы (наверное) создаются при старте приложения, но очередность создания непонятна. Вполне возможно, что в момент вызова
class constructor TTest1.Create;
begin
  FEncoding := TEncoding.GetEncoding(1251);
  FObj := TObject.Create;
end;

класс TEncoding или класс TMBCSEncoding, к которому идет обращение внутри, еще не существует, и экземпляр TMBCSEncoding не может быть создан. Логично ожидать Access Violation в таком случае, однако, вероятно, там предусмотрены пути обхода.

Когда же вы вызываете GetEncoding как геттер при действиях на форме, к этому моменту приложение уже живет полной жизнью и все классы давно созданы.

Ну, может, не совсем так, но засада где-то рядом. Еще не созданы все классы, а вы уже хотите создать экземпляр.
12 июн 20, 15:23    [22149846]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
shalamyansky
Member

Откуда:
Сообщений: 156
Если я прав, то ваш обход работает лишь до поры, до времени. В один прекрасный момент компилятор решит изменить порядок вызова конструкторов, и вы опять напоретесь на ту же ошибку.
12 июн 20, 15:29    [22149851]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11913
shalamyansky
класс TEncoding или класс TMBCSEncoding, к которому идет обращение внутри, еще не существует
Но если объявить FEncoding: TMBCSEncoding или свойство возвращать не доступом к полю, а геттером, то все работает
shalamyansky
Логично ожидать Access Violation в таком случае, однако, вероятно, там предусмотрены пути обхода.
Это не пути обхода. Это баг. При циклической ссылки юнитов мы получаем ошибку компиляции, а не попытки компилятора это пропустить. Построить дерево зависимостей для выполнении initialize секции модулей у компилятора проблем не вызывает
shalamyansky
Когда же вы вызываете GetEncoding как геттер при действиях на форме, к этому моменту приложение уже живет полной жизнью и все классы давно созданы.
Одна проблема - классовый конструктор вызывается в момент старта приложения, а не в момент первого обращения к классу.

Попробуйте сделать так
type
  TTest1 = class
  strict private
    class var
      FEncoding: TEncoding;
      FObj: TObject;
  strict private
    class constructor Create;
  public
    class property Encoding: TEncoding read FEncoding;
  end;

class constructor TTest1.Create;
begin
  ShowMessage('Create')
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LEnc: TObject;
begin
  LEnc := TTest1.Encoding;
end;
а потом замените выделенное на любой другой класс. И расскажите потом о порядке создания

Kazantsev Alexey
Можно репортить.
Голосуем
12 июн 20, 15:55    [22149863]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
Kazantsev Alexey
Member

Откуда:
Сообщений: 4469
_Vasilisk_
Голосуем

Я уже :)
12 июн 20, 16:00    [22149866]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
shalamyansky
Member

Откуда:
Сообщений: 156
Собрал ваш пример. Заметил следующее - если создать экземпляр вашего класса TTest1, или даже не создавать экземпляр, а хотя бы зарезервировать ссылку для него, например вот так:

TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    Test1 : TTest1;
  public
  end;

то пример работает и с TEncoding.

То есть причина не в классе TEncoding как таковом, а в неких зависимостях, которые влияют прежде всего на порядок исполнения начальных вызовов.

Класс же TEncoding не похож на класс TMBCSEncoding и прочие тем, что для него не создается экземпляра. В случае вызова TEncoding.GetEncoding(1251), как у вас, создается экземпляр TMBCSEncoding.

Не знаю, можно ли это считать багом, но поведения хотелось бы более определенного. Отпишитесь, что скажут создатели, любопытно.
12 июн 20, 18:54    [22149923]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11913
shalamyansky
, а хотя бы зарезервировать ссылку для него,
Документация говорит, что код классовых конструкторов вставляется в секцию initialization модуля, где объявлен класс, но только в том случае, если класс используется где-то в программе. А как его использовать: обратиться к методу или объявить переменную - не важно

shalamyansky
Класс же TEncoding не похож на класс TMBCSEncoding и прочие тем, что для него не создается экземпляра.
Для TStream тоже не предусмотрено создание экземпляра класса. Тем не менее с TStream все работает

shalamyansky
Отпишитесь, что скажут создатели,
Из моего опыта общения с багтрекером - ничего не скажут
12 июн 20, 19:57    [22149957]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
shalamyansky
Member

Откуда:
Сообщений: 156
_Vasilisk_

А как его использовать: обратиться к методу или объявить переменную - не важно

Оказывается, важно. Важно даже, как объявить. Потому что если вот так работает:
TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    Test1 : TTest1;
  public
end;

То вот так уже нет:
TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
  public
end;

var
    Test1 : TTest1;


Собственно, я согласен, что это есть баг. Но вы попросили его объяснить, и я выдвинул гипотезу, на мой взгляд правдоподобную. Любая гипотеза нуждается в доказательствах, но пока их нет, я в неё просто верю :)
12 июн 20, 20:28    [22149972]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
alekcvp
Member

Откуда:
Сообщений: 2178
_Vasilisk_
shalamyansky
Класс же TEncoding не похож на класс TMBCSEncoding и прочие тем, что для него не создается экземпляра.
Для TStream тоже не предусмотрено создание экземпляра класса. Тем не менее с TStream все работает

Это что-то новое, что для TStream не предусмотрено создание экземпляра класса.
C TFile и TDirectory тоже работет?

Сообщение было отредактировано: 12 июн 20, 20:43
12 июн 20, 20:45    [22149988]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11913
alekcvp
Это что-то новое, что для TStream не предусмотрено создание экземпляра класса.
Так никто TStream.Create и не вызывает. Я говорю о зависимости между объявлением классового свойства и вызова классового конструктора
alekcvp
C TFile и TDirectory тоже работет?
Нет. Может и не должно. TFile это record без полей. Соответственно обращения TFile.Method и TMyClass.File.Method будут всегда эквивалентны и конструктор можно не дергать
15 июн 20, 15:30    [22151073]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11913
Test.dpr.54: TFile.IntegerToFileAttributes(0);
004D9848 33C0             xor eax,eax
004D984A E8A5BDFFFF       call TFile.IntegerToFileAttributes
Test.dpr.55: TTestOther.File_.IntegerToFileAttributes(0);
004D984F 33C0             xor eax,eax
004D9851 E89EBDFFFF       call TFile.IntegerToFileAttributes

Тогда как при обращении к объекту он ищется у конкретного класса
Test.dpr.53: LEnc := TTestEncoding.Encoding;
004D988C A1FC3D4E00       mov eax,[$004e3dfc]
004D9891 A3043E4E00       mov [$004e3e04],eax
15 июн 20, 15:34    [22151077]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
ВсеРазумный
Member

Откуда:
Сообщений: 287
_Vasilisk_

if TTest1.Obj <> nil then

...
 if TTest1.>>>Encoding<< <> nil then
...

  FObj.Free;
  FEncoding.Free;




А где freeAndNil ? Ты проверяешь что адрес объекта помечен как NIL то free не помечает адрес переменной в nil

И когда объект освобождён, - переменная остаётся со старыми данными.
16 июн 20, 19:54    [22151945]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
rgreat
Member

Откуда:
Сообщений: 6019
ВсеРазумный
А где freeAndNil ? Ты проверяешь что адрес объекта помечен как NIL то free не помечает адрес переменной в nil

И когда объект освобождён, - переменная остаётся со старыми данными.


"procedure TForm1.Button1Click(Sender: TObject);" не может быть вызвана после "class destructor TTest1.Destroy;"
16 июн 20, 20:37    [22151969]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
ВсеРазумный
Member

Откуда:
Сообщений: 287
rgreat,

В любом случае баг у меня не воспроизводится
Delphi 10.4 Version 27.0.37889.9797

+
Memo1
OK: TMBCSEncoding
OK: TObject
OK: TObject
OK: TMBCSEncoding


unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type
  TTest1 = class
  strict private
  class var
    FEncoding: TEncoding;
    FObj: TObject;
  strict private
    class constructor Create;
    class destructor Destroy;
  public
    class property Encoding: TEncoding read FEncoding;
    class property Obj: TObject read FObj;
  end;

var
  Form1: TForm1;

implementation

class constructor TTest1.Create;
begin
  FEncoding := TEncoding.GetEncoding(1251);
  FObj := TObject.Create;
end;

class destructor TTest1.Destroy;
begin
  FObj.Free;
  FEncoding.Free;
end;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  if TTest1.Obj <> nil then
    Memo1.Lines.Add('OK: ' + TTest1.Obj.ToString)
  else
    Memo1.Lines.Add('Fail');
  if TTest1.Encoding <> nil then
    Memo1.Lines.Add('OK: ' + TTest1.Encoding.ToString)
  else
    Memo1.Lines.Add('Fail');
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  if TTest1.Encoding <> nil then
    Memo1.Lines.Add('OK: ' + TTest1.Encoding.ToString)
  else
    Memo1.Lines.Add('Fail');
  if TTest1.Obj <> nil then
    Memo1.Lines.Add('OK: ' + TTest1.Obj.ToString)
  else
    Memo1.Lines.Add('Fail');
end;

end.
16 июн 20, 21:41    [22151996]     Ответить | Цитировать Сообщить модератору
 Re: Создание TEncoding в классовом конструкторе  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11913
ВсеРазумный
В любом случае баг у меня не воспроизводится
Тест некорректный. Ты обращаясь к TTest.Obj провоцируешь вызов конструктора. Попробуй так
type
  TTest1 = class
  strict private
  class var
    FEncoding: TEncoding;
  strict private
    class constructor Create;
    class destructor Destroy;
  public
    class property Encoding: TEncoding read FEncoding;
  end;

  TTest2 = class
  strict private
  class var
    FObj: TObject;
  strict private
    class constructor Create;
    class destructor Destroy;
  public
    class property Obj: TObject read FObj;
  end;
...........
procedure TForm1.Button2Click(Sender: TObject);
begin
  if TTest1.Encoding <> nil then
    Memo1.Lines.Add('OK: ' + TTest1.Encoding.ToString)
  else
    Memo1.Lines.Add('Fail');
  if TTest2.Obj <> nil then
    Memo1.Lines.Add('OK: ' + TTest1.Obj.ToString)
  else
    Memo1.Lines.Add('Fail');
end;
17 июн 20, 13:17    [22152310]     Ответить | Цитировать Сообщить модератору
Все форумы / Delphi Ответить