Попробуем разобраться.
Изначально в условии задачи уточнялось - OnPaint не используем.
Правильно это или нет - за условиями задачи, цель была прояснить, понимает ли собеседник поведение VCL в данном случае или нет.
Для упорствующих, конечно, готов очередной подводный камень, к которому мы придем в процессе объяснения, но впрочем по порядку.
Что было предложено в процессе обсуждения вопроса:
1. Зависимость от версий Delphi
2. Выравнивание текста или его многострочность.
3. Смена/пересоздание DC при вызове Memo.Lines[0]
4. Нарушение очереди сообщений из-за вызова обработчика OnClick и как следствие прочие неприятности...
5. Неправильное место вызова, DC "не готов" для вывода (про готовность я не понял, честно, ибо если канва не готова - будет ошибка, все прочее для эстетов :)
Начнем с первого пункта.
Именно в изначальной постановке задачи, зависимости как таковой нет.
В неправильных вариантах кода текст будет:
Но.. по всей видимости есть некоторые изменения, ибо вывод текста с уплывшим Brush/Pen/Font гарантированно можно было воспроизвести под Delphi 2007 (и даже под 2010 - если мне не отказывает память) в случае использования самого первого варианта кода под ХР и ниже:
Код под ХЕ4 такое поведение сегодня мне не показал, он просто не вывел текст. :)
На этом я заострю ваше внимание чуть ниже, а пока что второй пункт...
Выравнивание текста или многострочность.
Здесь по, всей видимости, рассматривалось то, что TextOut, в отличие от DrawText не может работать с (скажем так) навороченной строкой, ну, к примеру, выравнивание по правому краю (из-за форматирования) или то, что первая строка в Memo может представлять из себя просто sLineBreak.
Это все мимо, хотя уже даже за такое я бы лично выставил ребятам пиво - люблю тех, кто мыслит не стандартно (без шуток) :)
Поэтому перейдем к сути (пункт три):
Взяв в руки отладчик, мы сможем увидеть, что только один вариант кода (вышеозвученный из четырех) приводит к некорректному поведению.
Происходит это из-за того, что ранее полученный нами DC, не то чтобы пересоздается - он просто разрушается, и при вызове TextOut на разрушенный DC, конечно может произойти все что угодно (вплоть до некорректного вывода текста).
Происходит это по причине того, что вызов Memo1.Lines[0] осуществляется с участием вызова SendMessage, который приведет нас вот к этому участку кода:
Обратите внимание на вызов FreeDeviceContexts - именно здесь и происходит разрушение ранее полученного DC.
Таким образом, понимая что любой вызов SendMessage приводит к освобождению DC, мы можем реализовать вот такой код:
А что с вызовом "Memo1.Text"?
А здесь все очень просто, вызов Perform внутри данного метода обрабатывается самой VCL, поэтому и нет освобождения DC.
Впрочем... - пункт четыре.
Я думаю что его стоит пропустить, ибо исходя из объяснений по пункту три и так все понятно - очередь событий, конечно, может повлиять на исполнение кода, но это нужно достаточно хитро все написать :)
Эпилог:
Что я хотел услышать от человека, который проходит данный тест - это следующее:
Первый вариант кода не верен, по причине работы с некорректным DC, после его освобождения в обработчике MainWndProc.
По сути - это все :)
А теперь перейдем к ответу на пять с плюсом и рассмотрим пятый пункт :)
Пункт пять - DC не готов, нужно рисовать в OnPaint.
Хорошо, допустим он не готов, хотя такое понятие по сути никак не может быть использовано в VCL коде, оно присуще состоянию флагов непосредственно GDI хэндла, с которыми VCL не работает.
Допустим и напишем вот такой код:
Код отработает правильно и нарисует содержимое первой строки TMemo.
Возникает вопрос, почему вызов "Memo1.Lines[0]" не привел к пересозданию DC и текст вывелся правильно?
Здесь все просто, суть кроется в той же процедуре FreeDeviceContexts.
В обработчике OnPaint канвас создан, валиден и залочен, таким образом переменная ACanvas, даже после вызова "Memo1.Lines[0]" будет содержать правильный DC.
Так работает VCL, но мы попробуем ее обойти и все-же вывести текст в обход залоченого канваса.
Сделаем это следующим кодом:
Итак по порядку, как мы уже рассмотрели:
1. В обработчике кнопки мы получаем валидный DC формы
2. Вызовом "Memo1.Lines[0]" мы его разрушили
3. Через Repaint вызвали отрисовку формы где..
4. Через уже невалидный DC вывели текст на форму.
Работает?
Да еще как, но ведь не должен.
А где ж засада, ведь по логике мы хэндл канваса уже разрушили (что и было ранее объяснено), причем под обработчик OnPaint внутри VCL было создано новое DC и залочено, но почему же текст выводится через старое DC?
А давай-те ка поизучаем.
Пишем:
Изначально в условии задачи уточнялось - OnPaint не используем.
Правильно это или нет - за условиями задачи, цель была прояснить, понимает ли собеседник поведение VCL в данном случае или нет.
Для упорствующих, конечно, готов очередной подводный камень, к которому мы придем в процессе объяснения, но впрочем по порядку.
Что было предложено в процессе обсуждения вопроса:
1. Зависимость от версий Delphi
2. Выравнивание текста или его многострочность.
3. Смена/пересоздание DC при вызове Memo.Lines[0]
4. Нарушение очереди сообщений из-за вызова обработчика OnClick и как следствие прочие неприятности...
5. Неправильное место вызова, DC "не готов" для вывода (про готовность я не понял, честно, ибо если канва не готова - будет ошибка, все прочее для эстетов :)
Начнем с первого пункта.
Именно в изначальной постановке задачи, зависимости как таковой нет.
В неправильных вариантах кода текст будет:
- либо не выведен;
- либо будет выведен с искажениями, как на картинке ниже;
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 с углубленкой - рекомендую двинуть на него объем работ по разработке компонент. Или - практическое абсолютное понимание работу служб и сетевого транспорта, предлагаю двинуть его на наши сервера. Очень редко бывает и такое - отличное знание ассемблера, системных документированных и не очень структур, его я забираю себе - он будет помогать мне писать ядро защиты.
Конечно, понятное дело, что периодически программисты подменяют друг друга, но как правило все работают по своим профилям, к примеру я никогда не работаю с базами. Я конечно могу это сделать, но мой коллега сделает это гораздо быстрее и качественней чем я.
Из приведенного кода опытному человеку должно быть вполне очевидным, почему так происходит. Но не буду раскрывать интригу, может кому-то интересно будет разобраться.
ОтветитьУдалитьЯ бы хотел сказать про другое. Использование сложной системы неправильным образом ни к чему хорошему не приведет. Контекст устройства нельзя использовать так, как в примерах.
Сам наступал на такие грабли, когда в обработчике WM_PAINT выполнялась загрузка данных, и в исправно работавшем годами приложении стали происходить странные вещи.
Полностью согласен, за такую реализацию в боевом коде, голова отрывается сразу. Но для тестирование самое оно.
УдалитьА с хитрыми ошибками тоже периодически приходится сталкиваться, буквально на неделе приложение падало при старте в том месте где оно должно было вывести банальный MessageBox, оказалось данный вызов пробуждает ожидающую синхронизации нить, где она пытается обратиться к неинициализированным объектам (об ошибке инициализации которых и хотел предупредить энтот MessageBox).
Нарефакторили, называется :)
GetMem + FreeMem + GetMem... оп, а указатель снова валиден.
ОтветитьУдалитьВ примере выше DC выделяет BeginPaint, случайно он совпадает по числовому значению с нашим, ну так ничего удивительного, что мы снова можем на него выводить.
Нет, здесь ситуация такова, что восстанавливается действительно именно старый DC.
УдалитьНа самом деле это ошибка в GDI, но она тянется аж с NT4
Да какая же это ошибка? Это документированное поведение, связанное с тем, что в Window Manager есть кэш контекстов устройств, и есть он там со времен Win16.
УдалитьОбратите внимание на название функций — GetDC и ReleaseDC. Первая получает контекст устройства, вторая возвращает его обратно в кэш (в отличие от CreateWIndow и DestroyWindow, например).
У Реймонда Чена по этому поводу есть заметка What does the CS_OWNDC class style do?.
Верно, только не возвращает, ибо он там находится изначально с начала вызова GetDC, а выставляет ему флаг DCX_INUSE и очищает поле DCE->pwndOrg.
УдалитьТочнее снимает данный флаг :)
УдалитьА под ReactOS, судя по всему такого поведения не будет, т.к. структура чистится целиком: http://doxygen.reactos.org/dc/dfb/windc_8c_acf244fc91180159ecabe3f2ba75761e4.html
Удалить"восстанавливается действительно именно старый DC" - не уловил чем новый отличается от старого
УдалитьГрубо, GetDC вернет нам HDC "A", потом делаем ему ReleaseDC, вызываем BeginPaint, который возвращает нам HDC "В" (в данном коде он не используется), но при этом и HDC "A" и HDC "В" стали валидными и мы можем рисовать на любом из них. Простыми словами в данный момент времени у окна два валидных HDC, причем один из них проинициализировался повторно неявно.
УдалитьДобавьте код:
УдалитьBeginPaint(Handle, PS);
Assert(FakeDC = PS.hdc);
Станет видно, что GetDC вернет HDC "A", потом ReleaseDC вернет его в кэш. BeginPaint снова вызывает GetDC, который возвращает нам HDC "A" из кэша.
Это понятно, но стоить нам написать FakeDC := Canvas.Handle; как все перестанет работать и не смотря на то что PS.hdc будет содержать взятый из кэша хэндл, рисовать на нем не получится, но на том, что вернет BeginPaint вполне даже можно :)
УдалитьПроблема легко решается
ОтветитьУдалить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;
Вам не кажется, что нормальный программист на подобных вещах вообще не должен фокусироваться?
ОтветитьУдалитьПри чтении свойства, в результате множества вложенных вызовов происходит уничтожения объекта, который как бы вообще не причем. При этом работа программы начинает зависеть от порядка вызовов логически не связанных операторов.
Таких косячков может быть достаточно много, и имхо о профессионализме программиста незнание какого-то из них ничего не говорит.
Конечно не должен, более того 99 процентов с этим никогда и не столкнуться, поэтому тут нет никакой речи о профессионализме :)
УдалитьЭти нюансы важны только тем кто действительно хочет во всей этой кухне разобраться, про которую я пишу на страницах блога :)
Наверное Сергей имел ввиду другое. Задавать такие вопросы на собеседовании - ересь!
УдалитьЯ обновил текст статьи и добавил описание того, почему такие вопросы вообще задаются :)
УдалитьПонятно. Ну так то не на собеседовании (ибо человек уже принят), а для определения проф. ориентации... Тут все логично!
УдалитьНормальные вопросы. Ересь, это когда человек тычет пяткой в грудь "я программист", и при этом не может (или не хочет) разобраться что-к-чему..
ОтветитьУдалитьМы на собеседованиях тоже задаём вопросы, на которые не обязательно знать ответ. Оценивается подход к решению...
Александр, спасибо за задачку и ответ. Вообще такие тонкости всегда интересны.
Спасибо за поддержку :)
УдалитьПрисоединяюсь. "Нормальные вопросы".
УдалитьУ нас в DevExpress скорее будет признан правильным ответ: "Оторвать гениталии тому кто попытается отрисовать одним из предложенных способов" :)
ОтветитьУдалитьУ вас в DevExpress уже как год почти не могут поправить ошибку с Checked у DxBarItem при работе через TAction. Ее тут Розыч описывал, в том числе и решение проблемы. И когда я ее в очередной раз правлю ручками, возникает желание оторвать эти самые штуки вам там всем. :)
УдалитьКакой ID бага?
УдалитьДа действительно, все еще не закрыт.
УдалитьВот он: http://www.devexpress.com/Support/Center/Question/Details/B232418
ЗЫ: по самой ошибке более подробно здесь: http://alexander-bagel.blogspot.ru/2013/02/ribbon-devexpress.html
УдалитьГлава 10: Ошибки
Может быть будет интересно - http://18delphi.blogspot.ru/2013/04/vcl.html
ОтветитьУдалитьУ вас наверное приложение многопоточное. В VCL, как известно, огромное количество таких мест, которые неправильно работают в многопоточных приложениях.
УдалитьТоже кстати неплохой вопрос для понимания многопоточности, почему так делать нельзя:
if FVar = 0 then
begin
Lock;
try
FVar := ...;
finally
Unlock;
end;
Частично об этом я уже рассказывал.
Удалитьhttp://alexander-bagel.blogspot.ru/2013/04/blog-post.html
Таки многопоточность там не причём
ОтветитьУдалитьТогда как в таком коде Add может быть вызвано два раза?
Удалитьif FDeviceContext = 0 then
begin
FDeviceContext := GetDeviceContext;
Add(Self);
end;
Причем GetDeviceContext не может вернуть 0, там это проверяется.
А можно посмотреть на код тестов?
УдалитьДействительно очень странное поведение, раз нет многопоточности.
Там есть ProcessMessages. Со всеми "вытекающими".
УдалитьМинимальный код - попробую выделить.
>> Там есть ProcessMessages
УдалитьОппа, давно пытаюсь написать статью о нежелательности использования данной функции и различным засадам к которым это приводит, но до конца еще не набрал нормальный для статьи объем материала (хотя там уже озвучены ошибки и на синхронизации нитей и при использование асинхронного сетевого транспорта, да впрочем даже путаница с очередью обработки сообщений).
Ради эксперимента в одном из проектов она была принудительно запрещена, дабы народ научился строить правильную архитектуру (даешь нагрузку на GUI поток - метла и швабра в руки вычищать все это.)
Поэтому конечно давайте пример - лично для меня этот момент крайне интересен.
"Тогда как в таком коде Add может быть вызвано два раза?"
УдалитьХороший блин вопрос.. Придётся опять брать в руки отладчик.
Если засада именно из-за ProcessMessage - то гарантированно только рекурсивный вызов.
Удалить... правда не понятно пока что где? :))
Удалить"Оппа, давно пытаюсь написать статью о нежелательности использования данной функции и различным засадам к которым это приводит"
УдалитьПОЛНОСТЬЮ согласен, но жизнь так устроена, что "за всем не уследишь".
"Ради эксперимента в одном из проектов она была принудительно запрещена, дабы народ научился строить правильную архитектуру (даешь нагрузку на GUI поток - метла и швабра в руки вычищать все это.)"
УдалитьСкажу ЛИЧНО ЗА СЕБЯ. Я раза ТРИ (а может и больше) пытался сделать отрисовку на TCanvas в отдельном потоке. Зачем? Ну для построения того же Print-Preview.
Но! Ни разу у меня это не получилось.
Ибо сам по себе GDI - потоконебезопасен. Что усугубляется кешом шрифтов (и прочих ресурсов) от Borland.
Может быть - "руки кривые". Согласен. Но времени в отладке было потрачено немало. И поскольку ошибки - плавающие (что и понятно) - в итоге "идеального варианта" - так и не сделал.
Посему ИМЕННО с Print-preview (а документы ой какие большие бывают, по нескольку тысяч страниц) - я ОСОЗНАННО остановился на ProcessMessages.
Хотя и ПОНИМАЮ его "грабли". И НЕ РАЗ на них уже наступал.
Кстати приведу ещё одну ссылку.
Покритикуйте пожалуйста - http://18delphi.blogspot.ru/2013/05/blog-post_7718.html
"Поэтому конечно давайте пример - лично для меня этот момент крайне интересен."
УдалитьПостараюсь в ближайшее время.
>> Но! Ни разу у меня это не получилось.
УдалитьДык HDC привязан к тому потоку, который его создал. Неудивительно :)
>> Покритикуйте пожалуйста
Неудобно, к примеру мне нужен только параметр anImageIndex, но к нему придется пробираться через остальные дефолтовые - проще как в API, а именно структура на вход.
Правда, если это внутренний код, проще просто сделать несколько конструкторов с нужным функционалом.
"Дык HDC привязан к тому потоку, который его создал. Неудивительно :)"
Удалить-- не ну понятно, что HDC получался в нужном потоке :-) До этого уж догадался :-)
А вот висло регулярно на Canvas.Font.Handle или Canvas.Brush.Handle (не в моём коде, а в коде Graphics). И побороть - так и не получилось. Хотя вроде бы объекты синхронизации - использовались.
Объекты синхронизации тут не помогут.
УдалитьВсе GDI работает через DCE (кэш канвы) и будет работать только с той нитью, к которой привязан.конкретный HDC.
При вызове функций GDI у переданного ей HDC проверяется параметр ptiOwner, представляющий из себя структуру PTHREADINFO соответствующего DCE. Именно тут и происходит отлуп.
Т.е. TCanvas (и Graphics) и многопоточность не совместимы? Я правильно понял?
УдалитьКонечно.
УдалитьНа всякий случай: 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."
"Конечно."
УдалитьСпасибо! Я так когда-то и "понял интуитивно".
Хотя...
"На всякий случай: 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. К ним такие же правила применяются?
Я правильно понял?
"и различным засадам к которым это приводит" - самое банальное", что "лежит на поверхности" это зависание приложения в бесконечном цикле обработки сообщений :-) когда внутри ProcessMessages порождаются другие сообщения :-)
Удалить"Неудобно, к примеру мне нужен только параметр anImageIndex, но к нему придется пробираться через остальные дефолтовые - проще как в API, а именно структура на вход.
ОтветитьУдалитьПравда, если это внутренний код, проще просто сделать несколько конструкторов с нужным функционалом"
- ну я не про "удобство" вёл речь, а именно в "контексте многопоточности", хотя и за это спасибо :-)
В данном контексте никаких выводов сделпть нельзя, ибо неясна модель наследования. Может быть именно тут ошибки и нет, но вот в родителях...
УдалитьНе. КОНКРЕТНО это класс - САМОДОСТАТОЧЕН. Он к Print-Preview НИКАКОГО отношения не имеет. Это - ДРУГОЙ пример.
УдалитьЕДИНСТВЕННЫЙ кстати, который таки заработал в многопоточности. Я думал - может найдётся критика экспертов. У меня у самого есть там "подозрения", что не всё гладко. Только "не могу объяснить" в чём.
Разве что за Cleаnup можно немного поругать...
УдалитьБлин! Конечно! Не ту ссылку дал :-(
УдалитьВот правильная ссылка - http://18delphi.blogspot.ru/2013/05/blog-post_8549.html
А на предыдущий класс можно вообще не смотреть. Он - "ни о чём".
"Разве что за Cleаnup можно немного поругать..."
Удалитьprocedure TafwLongProcessVisualizer.Cleanup;
begin
FreeAndNil(f_Wnd);
inherited;
end;//TafwLongProcessVisualizer.Cleanup
А там-то что не так?
>> Блин! Конечно! Не ту ссылку дал :-(
УдалитьХм, а эть уже смотреть нужно и явно чуть позже :)
>> А там-то что не так?
Этот код должен быть расположен в деструкторе.
"Этот код должен быть расположен в деструкторе."
УдалитьА. Ок. Считайте, что это это - "деструктор". Просто я использую подсчёт ссылок на базовом классе и деструктор НИКОГДА не перекрываю.
Допустим.
УдалитьВпрочем жду варианта кода в котором воспроизводится вышеописанная Вами ошибка.
Хочется уже ее покрутить в отладчике :)
"Хм, а эть уже смотреть нужно и явно чуть позже :)"
УдалитьСПАСИБО заранее. Буду ОЧЕНЬ признателен.
"Допустим."
УдалитьПро подсчёт ссылок я писал (если интересно) тут - http://18delphi.blogspot.ru/2013/04/iunknown.html
"Впрочем жду варианта кода в котором воспроизводится вышеописанная Вами ошибка.
Хочется уже ее покрутить в отладчике :)"
Александр. Специально для вас - ЗАВТРА же подниму CVS/SVN/CQ и поищу симптомы ошибки и выделю "минимальный тест". Без "нашего кода". Просто это когда-то было сделано, но потом потерялось :-( Лет 10-ть уже наверное прошло. Но вы со своей темой - меня - раззадорили. Надо "точки расставить".А не то что - "работает и ладно".
Можно ещё вопрос? Не по теме.
ОтветитьУдалитьВот тут - http://18delphi.blogspot.ru/2013/04/docktree.html
есть правки СТАНДАРТНОГО TDockTree. Ибо в нём были утечки и проезды по памяти. Может быть кто-нибудь не сочтёт за труд посмотреть дифу правок? Или стоит прямо построчно её отдельно опубликовать?
Или может быть никто не пользуется докингом в Delphi? Ибо удивительно - как с таким количеством ошибок "это" может работать.
Спасибо заранее.
Когда будет вторая часть ответа?
ОтветитьУдалитьЧуть попозже, как пересилю свою лень :)
УдалитьНа днях будет статья по Runtime Error 217, а потом нужно будет Process Memory Map обновить, чтобы можно было более наглядно продемонстрировать вторую часть ответа на вопрос.
С нетерпением жду :)
Удалить