понедельник, 23 декабря 2013 г.

Ответ на задачку №2, часть первая

Попробуем разобраться.

Изначально в условии задачи уточнялось - OnPaint не используем.
Правильно это или нет - за условиями задачи, цель была прояснить, понимает ли собеседник поведение VCL в данном случае или нет.
Для упорствующих, конечно, готов очередной подводный камень, к которому мы придем в процессе объяснения, но впрочем по порядку.

Что было предложено в процессе обсуждения вопроса:
1. Зависимость от версий Delphi
2. Выравнивание текста или его многострочность.
3. Смена/пересоздание DC при вызове Memo.Lines[0]
4. Нарушение очереди сообщений из-за вызова обработчика OnClick и как следствие прочие неприятности...
5. Неправильное место вызова, DC "не готов" для вывода (про готовность я не понял, честно, ибо если канва не готова - будет ошибка, все прочее для эстетов :)

Начнем с первого пункта.


Именно в изначальной постановке задачи, зависимости как таковой нет.
В неправильных вариантах кода текст будет:
  1. либо не выведен;
  2. либо будет выведен с искажениями, как на картинке ниже;


Но.. по всей видимости есть некоторые изменения, ибо вывод текста с уплывшим Brush/Pen/Font гарантированно можно было воспроизвести под Delphi 2007 (и даже под 2010 - если мне не отказывает память) в случае использования самого первого варианта кода под ХР и ниже:

var
  ACanvas: THandle;
  AText: string;
begin
  ACanvas := Canvas.Handle;
  AText := Memo1.Lines[0];
  TextOut(ACanvas, Button1.Left, 20, PChar(AText), Length(AText));
end;

Код под ХЕ4 такое поведение сегодня мне не показал, он просто не вывел текст. :)

На этом я заострю ваше внимание чуть ниже, а пока что второй пункт...

Выравнивание текста или многострочность.

Здесь по, всей видимости, рассматривалось то, что TextOut, в отличие от DrawText не может работать с (скажем так) навороченной строкой, ну, к примеру, выравнивание по правому краю (из-за форматирования) или то, что первая строка в Memo может представлять из себя просто sLineBreak.
Это все мимо, хотя уже даже за такое я бы лично выставил ребятам пиво - люблю тех, кто мыслит не стандартно (без шуток) :)

Поэтому перейдем к сути (пункт три):

Взяв в руки отладчик, мы сможем увидеть, что только один вариант кода (вышеозвученный из четырех) приводит к некорректному поведению.
Происходит это из-за того, что ранее полученный нами DC, не то чтобы пересоздается - он просто разрушается, и при вызове TextOut на разрушенный DC, конечно может произойти все что угодно (вплоть до некорректного вывода текста).

Происходит это по причине того, что вызов Memo1.Lines[0] осуществляется с участием вызова SendMessage, который приведет нас вот к этому участку кода:

procedure TWinControl.MainWndProc(var Message: TMessage);
begin
  try
    try
      WindowProc(Message);
    finally
      FreeDeviceContexts;
      FreeMemoryContexts;
    end;
  except
    Application.HandleException(Self);
  end;
end;

Обратите внимание на вызов FreeDeviceContexts - именно здесь и происходит разрушение ранее полученного DC.

Таким образом, понимая что любой вызов SendMessage приводит к освобождению DC, мы можем реализовать вот такой код:

procedure TForm1.Button2Click(Sender: TObject);
var
  ACanvas: THandle;
  AText: string;
begin
  AText := 'qweqweqwe';
  ACanvas := Canvas.Handle;
  SendMessage(Handle, WM_NULL, 0, 0); // << после данного вызова канвас не валиден
  TextOut(ACanvas, Button1.Left, 20, PChar(AText), Length(AText));
end;

А что с вызовом "Memo1.Text"?
А здесь все очень просто, вызов Perform внутри данного метода обрабатывается самой VCL, поэтому и нет освобождения DC.

Впрочем... - пункт четыре.
Я думаю что его стоит пропустить, ибо исходя из объяснений по пункту три и так все понятно - очередь событий, конечно, может повлиять на исполнение кода, но это нужно достаточно хитро все написать :)

Эпилог:
Что я хотел услышать от человека, который проходит данный тест - это следующее:
Первый вариант кода не верен, по причине работы с некорректным DC, после его освобождения в обработчике MainWndProc.
По сути - это все :)

А теперь перейдем к ответу на пять с плюсом и рассмотрим пятый пункт :)

Пункт пять - DC не готов, нужно рисовать в OnPaint.

Хорошо, допустим он не готов, хотя такое понятие по сути никак не может быть использовано в VCL коде, оно присуще состоянию флагов непосредственно GDI хэндла, с которыми VCL не работает.

Допустим и напишем вот такой код:

procedure TForm1.FormPaint(Sender: TObject);
var
  ACanvas: THandle;
  AText: string;
begin
  ACanvas := Canvas.Handle;
  AText := Memo1.Lines[0];
  TextOut(ACanvas, Button1.Left, 20, PChar(AText), Length(AText));
end;

Код отработает правильно и нарисует содержимое первой строки TMemo.
Возникает вопрос, почему вызов "Memo1.Lines[0]" не привел к пересозданию DC и текст вывелся правильно?
Здесь все просто, суть кроется в той же процедуре FreeDeviceContexts.
В обработчике OnPaint канвас создан, валиден и залочен, таким образом переменная ACanvas, даже после вызова "Memo1.Lines[0]" будет содержать правильный DC.

Так работает VCL, но мы попробуем ее обойти и все-же вывести текст в обход залоченого канваса.

Сделаем это следующим кодом:

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure FormPaint(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    ACanvas: THandle;
    AText: string;
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  ACanvas := Canvas.Handle;
  AText := Memo1.Lines[0];
  Repaint;
end;
 
procedure TForm1.FormPaint(Sender: TObject);
begin
  if ACanvas <> 0 then
    TextOut(ACanvas, Button1.Left, 20, PChar(AText), Length(AText));
end;

Итак по порядку, как мы уже рассмотрели:
1. В обработчике кнопки мы получаем валидный DC формы
2. Вызовом "Memo1.Lines[0]" мы его разрушили
3. Через Repaint вызвали отрисовку формы где..
4. Через уже невалидный DC вывели текст на форму.

Работает?
Да еще как, но ведь не должен.
А где ж засада, ведь по логике мы хэндл канваса уже разрушили (что и было ранее объяснено), причем под обработчик OnPaint внутри VCL было создано новое DC и залочено, но почему же текст выводится через старое DC?

А давай-те ка поизучаем.

Пишем:

procedure TForm1.Button3Click(Sender: TObject);
var
  FakeDC: HDC;
  AText: string;
begin
  AText := 'Некий текст';
  // Создаем DC
  FakeDC := GetDC(Handle);
  // Сразу его релизим
  ReleaseDC(Handle, FakeDC);
  // Проверочка
  if WindowFromDC(FakeDC) <> 0 then
    ShowMessage('FakeDC не разрушен');
  TextOut(FakeDC, Button1.Left, 20, PChar(AText), Length(AText));
end;

Здесь, выкинув лишнее и взяв суть из VCL кода демонстрируется что происходит с DC при выполнении всех вышеописанных строчек кода. Вдогонку мы проверяем - действительно ли DC не валиден вызовом WindowFromDC.

А теперь.... Магия :))

Пишем:

procedure TForm1.Button3Click(Sender: TObject);
var
  FakeDC: HDC;
  AText: string;
  PS: TPaintStruct;
begin
  AText := 'Некий текст';
  // Создаем DC
  FakeDC := GetDC(Handle);
  // Сразу его релизим
  ReleaseDC(Handle, FakeDC);
  // Проверочка
  if WindowFromDC(FakeDC) <> 0 then
    ShowMessage('FakeDC не разрушен');

  // Оживляем разрушенный DC
  InvalidateRect(Handle, 0, True);
  BeginPaint(Handle, PS);
  // Проверка, привязан ли разрушенный DC к нашей форме?
  if WindowFromDC(FakeDC) = Handle then
    // оппа - привязан, а давай-ка выведем на нем текст...
    TextOut(FakeDC, Button1.Left, 20, PChar(AText), Length(AText));
  EndPaint(Handle, PS);
end;

А вот это уже крайне интересно, работая с заведомо разрушенным DC, мы все же имеем возможность вывода текста на форму, именно поэтому и работает вариант с отложенной отрисовкой через OnPaint.

Впрочем... здесь я пока что и остановлюсь.

Это была только часть ответа на задачку, во второй части я объясню как такое может происходить, но...

Но не раньше, чем будут предложены варианты, объясняющие такое поведение, или хотя бы предположения о таком поведении :)
Думаю после НГ - всех с наступающим, кстати :)

UPD:
Здесь я постараюсь дать развернутый ответ на вопросы - зачем давать такие примеры кода на собеседовании? :)

Итак, начнем с того, что IT отдел у нас достаточно маленький (8 человек) а объем работы категорически большой/
Поэтому при отборе сотрудников мы пользуемся старым шаблоном - нам не нужны ваши дипломы/пол/возраст/вес/социальный статус/связи и прочее, нам нужны только ваши знания и мы за них готовы платить ну очень хорошие деньги, даже для Москвы :)
Но знания должны быть хорошие, ибо как правило мы берем разработчика один раз и навсегда, попрыгунчики нам не нужны, поэтому только квалификация и еще раз квалификация.
Текучка очень маленькая - на коммерческом коде ушел всего один человек за последние 10 лет и то по семейным обстоятельствам, о чем кстати до сих пор жалеем, уж очень был хорош.

Собеседование проходит в три этапа - сначала через онлайн с соискателем беседуют программисты на предмет - он вообще к нам подойдет? Этот этап простой, без вот таких вот мудрых вопросов (хотя, просто для понимания, в некоторых случаях могут задаваться и они). На этом этапе и происходит отсев соискателей, очень сложно найти человека той квалификации, которая требуется.

Второй этап: IT отдел выдал вердикт - нам он нравится, после которого идет небольшое согласование у начальства и соискатель вызывается на допрос к техническому директору.
Если он его проходит (а как правило проходит, ибо мы стараемся чтобы технический не тратил свое время на совсем уж студентов), то он приглашается на беседу с Генеральным, после чего происходит принятие на работу.

Все - после этого человек официально трудоустроен, заключен договор и прочее.

И вот тут уже доходит очередь и до меня, где я прогоняю человека через несколько тестов (их сейчас 19 - если не ошибаюсь) на основании которых я понимаю в каких областях человек наиболее силен и где его применение будет наиболее выгодно для компании.
Грубо резюмы выглядят примерно так:
отличные знания VCL с углубленкой - рекомендую двинуть на него объем работ по разработке компонент. Или - практическое абсолютное понимание работу служб и сетевого транспорта, предлагаю двинуть его на наши сервера. Очень редко бывает и такое - отличное знание ассемблера, системных документированных и не очень структур, его я забираю себе - он будет помогать мне писать ядро защиты.

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

63 комментария:

  1. Из приведенного кода опытному человеку должно быть вполне очевидным, почему так происходит. Но не буду раскрывать интригу, может кому-то интересно будет разобраться.

    Я бы хотел сказать про другое. Использование сложной системы неправильным образом ни к чему хорошему не приведет. Контекст устройства нельзя использовать так, как в примерах.

    Сам наступал на такие грабли, когда в обработчике WM_PAINT выполнялась загрузка данных, и в исправно работавшем годами приложении стали происходить странные вещи.

    ОтветитьУдалить
    Ответы
    1. Полностью согласен, за такую реализацию в боевом коде, голова отрывается сразу. Но для тестирование самое оно.
      А с хитрыми ошибками тоже периодически приходится сталкиваться, буквально на неделе приложение падало при старте в том месте где оно должно было вывести банальный MessageBox, оказалось данный вызов пробуждает ожидающую синхронизации нить, где она пытается обратиться к неинициализированным объектам (об ошибке инициализации которых и хотел предупредить энтот MessageBox).
      Нарефакторили, называется :)

      Удалить
  2. GetMem + FreeMem + GetMem... оп, а указатель снова валиден.

    В примере выше DC выделяет BeginPaint, случайно он совпадает по числовому значению с нашим, ну так ничего удивительного, что мы снова можем на него выводить.

    ОтветитьУдалить
    Ответы
    1. Нет, здесь ситуация такова, что восстанавливается действительно именно старый DC.
      На самом деле это ошибка в GDI, но она тянется аж с NT4

      Удалить
    2. Да какая же это ошибка? Это документированное поведение, связанное с тем, что в Window Manager есть кэш контекстов устройств, и есть он там со времен Win16.
      Обратите внимание на название функций — GetDC и ReleaseDC. Первая получает контекст устройства, вторая возвращает его обратно в кэш (в отличие от CreateWIndow и DestroyWindow, например).
      У Реймонда Чена по этому поводу есть заметка What does the CS_OWNDC class style do?.

      Удалить
    3. Верно, только не возвращает, ибо он там находится изначально с начала вызова GetDC, а выставляет ему флаг DCX_INUSE и очищает поле DCE->pwndOrg.

      Удалить
    4. А под ReactOS, судя по всему такого поведения не будет, т.к. структура чистится целиком: http://doxygen.reactos.org/dc/dfb/windc_8c_acf244fc91180159ecabe3f2ba75761e4.html

      Удалить
    5. "восстанавливается действительно именно старый DC" - не уловил чем новый отличается от старого

      Удалить
    6. Грубо, GetDC вернет нам HDC "A", потом делаем ему ReleaseDC, вызываем BeginPaint, который возвращает нам HDC "В" (в данном коде он не используется), но при этом и HDC "A" и HDC "В" стали валидными и мы можем рисовать на любом из них. Простыми словами в данный момент времени у окна два валидных HDC, причем один из них проинициализировался повторно неявно.

      Удалить
    7. Добавьте код:
      BeginPaint(Handle, PS);
      Assert(FakeDC = PS.hdc);
      Станет видно, что GetDC вернет HDC "A", потом ReleaseDC вернет его в кэш. BeginPaint снова вызывает GetDC, который возвращает нам HDC "A" из кэша.

      Удалить
    8. Это понятно, но стоить нам написать FakeDC := Canvas.Handle; как все перестанет работать и не смотря на то что PS.hdc будет содержать взятый из кэша хэндл, рисовать на нем не получится, но на том, что вернет BeginPaint вполне даже можно :)

      Удалить
  3. Проблема легко решается

    var
    ACanvas: THandle;
    AText: string;
    begin
    ACanvas := Canvas.Handle;
    Canvas.Lock;
    AText := Memo1.Lines[0];
    TextOut(ACanvas, Button1.Left, 20, PChar(AText), Length(AText));
    Canvas.Unlock;
    end;

    ОтветитьУдалить
  4. Вам не кажется, что нормальный программист на подобных вещах вообще не должен фокусироваться?
    При чтении свойства, в результате множества вложенных вызовов происходит уничтожения объекта, который как бы вообще не причем. При этом работа программы начинает зависеть от порядка вызовов логически не связанных операторов.
    Таких косячков может быть достаточно много, и имхо о профессионализме программиста незнание какого-то из них ничего не говорит.

    ОтветитьУдалить
    Ответы
    1. Конечно не должен, более того 99 процентов с этим никогда и не столкнуться, поэтому тут нет никакой речи о профессионализме :)
      Эти нюансы важны только тем кто действительно хочет во всей этой кухне разобраться, про которую я пишу на страницах блога :)

      Удалить
    2. Наверное Сергей имел ввиду другое. Задавать такие вопросы на собеседовании - ересь!

      Удалить
    3. Я обновил текст статьи и добавил описание того, почему такие вопросы вообще задаются :)

      Удалить
    4. Понятно. Ну так то не на собеседовании (ибо человек уже принят), а для определения проф. ориентации... Тут все логично!

      Удалить
  5. Нормальные вопросы. Ересь, это когда человек тычет пяткой в грудь "я программист", и при этом не может (или не хочет) разобраться что-к-чему..
    Мы на собеседованиях тоже задаём вопросы, на которые не обязательно знать ответ. Оценивается подход к решению...

    Александр, спасибо за задачку и ответ. Вообще такие тонкости всегда интересны.

    ОтветитьУдалить
  6. У нас в DevExpress скорее будет признан правильным ответ: "Оторвать гениталии тому кто попытается отрисовать одним из предложенных способов" :)

    ОтветитьУдалить
    Ответы
    1. У вас в DevExpress уже как год почти не могут поправить ошибку с Checked у DxBarItem при работе через TAction. Ее тут Розыч описывал, в том числе и решение проблемы. И когда я ее в очередной раз правлю ручками, возникает желание оторвать эти самые штуки вам там всем. :)

      Удалить
    2. Какой ID бага?

      Удалить
    3. Да действительно, все еще не закрыт.
      Вот он: http://www.devexpress.com/Support/Center/Question/Details/B232418

      Удалить
    4. ЗЫ: по самой ошибке более подробно здесь: http://alexander-bagel.blogspot.ru/2013/02/ribbon-devexpress.html
      Глава 10: Ошибки

      Удалить
  7. Может быть будет интересно - http://18delphi.blogspot.ru/2013/04/vcl.html

    ОтветитьУдалить
    Ответы
    1. У вас наверное приложение многопоточное. В VCL, как известно, огромное количество таких мест, которые неправильно работают в многопоточных приложениях.
      Тоже кстати неплохой вопрос для понимания многопоточности, почему так делать нельзя:
      if FVar = 0 then
      begin
      Lock;
      try
      FVar := ...;
      finally
      Unlock;
      end;

      Удалить
    2. Частично об этом я уже рассказывал.
      http://alexander-bagel.blogspot.ru/2013/04/blog-post.html

      Удалить
  8. Таки многопоточность там не причём

    ОтветитьУдалить
    Ответы
    1. Тогда как в таком коде Add может быть вызвано два раза?
      if FDeviceContext = 0 then
      begin
      FDeviceContext := GetDeviceContext;
      Add(Self);
      end;
      Причем GetDeviceContext не может вернуть 0, там это проверяется.

      Удалить
    2. А можно посмотреть на код тестов?
      Действительно очень странное поведение, раз нет многопоточности.

      Удалить
    3. Там есть ProcessMessages. Со всеми "вытекающими".

      Минимальный код - попробую выделить.

      Удалить
    4. >> Там есть ProcessMessages
      Оппа, давно пытаюсь написать статью о нежелательности использования данной функции и различным засадам к которым это приводит, но до конца еще не набрал нормальный для статьи объем материала (хотя там уже озвучены ошибки и на синхронизации нитей и при использование асинхронного сетевого транспорта, да впрочем даже путаница с очередью обработки сообщений).
      Ради эксперимента в одном из проектов она была принудительно запрещена, дабы народ научился строить правильную архитектуру (даешь нагрузку на GUI поток - метла и швабра в руки вычищать все это.)
      Поэтому конечно давайте пример - лично для меня этот момент крайне интересен.

      Удалить
    5. "Тогда как в таком коде Add может быть вызвано два раза?"

      Хороший блин вопрос.. Придётся опять брать в руки отладчик.

      Удалить
    6. Если засада именно из-за ProcessMessage - то гарантированно только рекурсивный вызов.

      Удалить
    7. ... правда не понятно пока что где? :))

      Удалить
    8. "Оппа, давно пытаюсь написать статью о нежелательности использования данной функции и различным засадам к которым это приводит"

      ПОЛНОСТЬЮ согласен, но жизнь так устроена, что "за всем не уследишь".

      Удалить
    9. "Ради эксперимента в одном из проектов она была принудительно запрещена, дабы народ научился строить правильную архитектуру (даешь нагрузку на GUI поток - метла и швабра в руки вычищать все это.)"

      Скажу ЛИЧНО ЗА СЕБЯ. Я раза ТРИ (а может и больше) пытался сделать отрисовку на TCanvas в отдельном потоке. Зачем? Ну для построения того же Print-Preview.

      Но! Ни разу у меня это не получилось.

      Ибо сам по себе GDI - потоконебезопасен. Что усугубляется кешом шрифтов (и прочих ресурсов) от Borland.

      Может быть - "руки кривые". Согласен. Но времени в отладке было потрачено немало. И поскольку ошибки - плавающие (что и понятно) - в итоге "идеального варианта" - так и не сделал.

      Посему ИМЕННО с Print-preview (а документы ой какие большие бывают, по нескольку тысяч страниц) - я ОСОЗНАННО остановился на ProcessMessages.

      Хотя и ПОНИМАЮ его "грабли". И НЕ РАЗ на них уже наступал.

      Кстати приведу ещё одну ссылку.

      Покритикуйте пожалуйста - http://18delphi.blogspot.ru/2013/05/blog-post_7718.html

      Удалить
    10. "Поэтому конечно давайте пример - лично для меня этот момент крайне интересен."

      Постараюсь в ближайшее время.

      Удалить
    11. >> Но! Ни разу у меня это не получилось.
      Дык HDC привязан к тому потоку, который его создал. Неудивительно :)

      >> Покритикуйте пожалуйста
      Неудобно, к примеру мне нужен только параметр anImageIndex, но к нему придется пробираться через остальные дефолтовые - проще как в API, а именно структура на вход.
      Правда, если это внутренний код, проще просто сделать несколько конструкторов с нужным функционалом.

      Удалить
    12. "Дык HDC привязан к тому потоку, который его создал. Неудивительно :)"

      -- не ну понятно, что HDC получался в нужном потоке :-) До этого уж догадался :-)

      А вот висло регулярно на Canvas.Font.Handle или Canvas.Brush.Handle (не в моём коде, а в коде Graphics). И побороть - так и не получилось. Хотя вроде бы объекты синхронизации - использовались.

      Удалить
    13. Объекты синхронизации тут не помогут.
      Все GDI работает через DCE (кэш канвы) и будет работать только с той нитью, к которой привязан.конкретный HDC.
      При вызове функций GDI у переданного ей HDC проверяется параметр ptiOwner, представляющий из себя структуру PTHREADINFO соответствующего DCE. Именно тут и происходит отлуп.

      Удалить
    14. Т.е. TCanvas (и Graphics) и многопоточность не совместимы? Я правильно понял?

      Удалить
    15. На всякий случай: http://msdn.microsoft.com/en-us/library/windows/desktop/dd144871(v=vs.85).aspx
      "Note that the handle to the DC can only be used by a single thread at any one time."

      Удалить
    16. "Конечно."

      Спасибо! Я так когда-то и "понял интуитивно".

      Хотя...

      "На всякий случай: http://msdn.microsoft.com/en-us/library/windows/desktop/dd144871(v=vs.85).aspx
      "Note that the handle to the DC can only be used by a single thread at any one time.""

      Тут вот по-моему - недопонимание. Я же не про hDC (hDC - у меня как раз ГАРАНТИРОВАННО был правильный и от правильной нити) говорил, а про hFont и hBrush etc. К ним такие же правила применяются?

      Я правильно понял?

      Удалить
    17. "и различным засадам к которым это приводит" - самое банальное", что "лежит на поверхности" это зависание приложения в бесконечном цикле обработки сообщений :-) когда внутри ProcessMessages порождаются другие сообщения :-)

      Удалить
  9. "Неудобно, к примеру мне нужен только параметр anImageIndex, но к нему придется пробираться через остальные дефолтовые - проще как в API, а именно структура на вход.
    Правда, если это внутренний код, проще просто сделать несколько конструкторов с нужным функционалом"

    - ну я не про "удобство" вёл речь, а именно в "контексте многопоточности", хотя и за это спасибо :-)

    ОтветитьУдалить
    Ответы
    1. В данном контексте никаких выводов сделпть нельзя, ибо неясна модель наследования. Может быть именно тут ошибки и нет, но вот в родителях...

      Удалить
    2. Не. КОНКРЕТНО это класс - САМОДОСТАТОЧЕН. Он к Print-Preview НИКАКОГО отношения не имеет. Это - ДРУГОЙ пример.

      ЕДИНСТВЕННЫЙ кстати, который таки заработал в многопоточности. Я думал - может найдётся критика экспертов. У меня у самого есть там "подозрения", что не всё гладко. Только "не могу объяснить" в чём.

      Удалить
    3. Разве что за Cleаnup можно немного поругать...

      Удалить
    4. Блин! Конечно! Не ту ссылку дал :-(

      Вот правильная ссылка - http://18delphi.blogspot.ru/2013/05/blog-post_8549.html

      А на предыдущий класс можно вообще не смотреть. Он - "ни о чём".

      Удалить
    5. "Разве что за Cleаnup можно немного поругать..."

      procedure TafwLongProcessVisualizer.Cleanup;
      begin
      FreeAndNil(f_Wnd);
      inherited;
      end;//TafwLongProcessVisualizer.Cleanup

      А там-то что не так?

      Удалить
    6. >> Блин! Конечно! Не ту ссылку дал :-(
      Хм, а эть уже смотреть нужно и явно чуть позже :)

      >> А там-то что не так?
      Этот код должен быть расположен в деструкторе.

      Удалить
    7. "Этот код должен быть расположен в деструкторе."

      А. Ок. Считайте, что это это - "деструктор". Просто я использую подсчёт ссылок на базовом классе и деструктор НИКОГДА не перекрываю.

      Удалить
    8. Допустим.
      Впрочем жду варианта кода в котором воспроизводится вышеописанная Вами ошибка.
      Хочется уже ее покрутить в отладчике :)

      Удалить
    9. "Хм, а эть уже смотреть нужно и явно чуть позже :)"

      СПАСИБО заранее. Буду ОЧЕНЬ признателен.

      Удалить
    10. "Допустим."

      Про подсчёт ссылок я писал (если интересно) тут - http://18delphi.blogspot.ru/2013/04/iunknown.html

      "Впрочем жду варианта кода в котором воспроизводится вышеописанная Вами ошибка.
      Хочется уже ее покрутить в отладчике :)"

      Александр. Специально для вас - ЗАВТРА же подниму CVS/SVN/CQ и поищу симптомы ошибки и выделю "минимальный тест". Без "нашего кода". Просто это когда-то было сделано, но потом потерялось :-( Лет 10-ть уже наверное прошло. Но вы со своей темой - меня - раззадорили. Надо "точки расставить".А не то что - "работает и ладно".

      Удалить
  10. Можно ещё вопрос? Не по теме.

    Вот тут - http://18delphi.blogspot.ru/2013/04/docktree.html

    есть правки СТАНДАРТНОГО TDockTree. Ибо в нём были утечки и проезды по памяти. Может быть кто-нибудь не сочтёт за труд посмотреть дифу правок? Или стоит прямо построчно её отдельно опубликовать?

    Или может быть никто не пользуется докингом в Delphi? Ибо удивительно - как с таким количеством ошибок "это" может работать.

    Спасибо заранее.

    ОтветитьУдалить
  11. Когда будет вторая часть ответа?

    ОтветитьУдалить
    Ответы
    1. Чуть попозже, как пересилю свою лень :)
      На днях будет статья по Runtime Error 217, а потом нужно будет Process Memory Map обновить, чтобы можно было более наглядно продемонстрировать вторую часть ответа на вопрос.

      Удалить
    2. С нетерпением жду :)

      Удалить