Список из рекордов на коленке

Честно подсмотрено.
THeader = record
x,y,z: TMyRecordField;
NextLabel: packed record end;
end;

На сколько прекрасно, на столько и отвратительно. Но восхищает однозначно хотя бы тем, что это вообще

Читать на сайте автора.

Парное_программирование VS ревью_кода

 Данная заметка — реакция на статью http://alexnesterov.com/code-review/, где практика code review рассматривается как антипаттерн, а парное программирование как способ решить задачи ревью кода правильно. Конечно же, у парного программирования

Читать на сайте автора.

Почему тип анонимных функций не всегда полезен или о проблема присвоения reference to типу неанонимных функций

Сегодня снова об особенностях анонимных функций.
Два предыдущих поста:

  1. Просто баг в коде или особенность счетчика ссылок в замыканиях
  2. Немного про замыкание ( Одно замыкание на несколько анонимных функций в одном контексте)

  У типа анонимный функций есть замечательная возможность. В качестве обработчика можно присваивать не только анонимные функции, но и обычные методы и статические функции. Казалось бы, нет причин почему не использовать тип reference to всегда.
Однако есть ситуации, когда reference to тип может сыграть злую шутку. И я сейчас не про вопрос производительности, о котором я думаю большинство осведомлено. 
Проблема может возникнуть при передаче обычной процедуры или метода в параметр с анонимным типом. Покажем на примере. 
Наиболее распространенный вариант использования процедурных типов в реализации подписки на события.
  Пусть есть какой-нибудь список EventHandlers, и конечно же, как у правильных хипстеров он будет параметризован типом анонимной функций TList<TProc>. 
Заведем два метода для возможности подписаться на событие и отписаться от него:  Subscribe(AHandler: TProc) и Unsubscribe(AHandler: TProc): Boolean соответственно. Однако, крайне вероятно, вас постигнет неудача если в качестве обработчика вы используете неанонимную функцию.  По крайне мере отписаться вы уже не сможете. 

Вот код примера:

program ReferenceToProc;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Generics.Collections;
var
eventHandlers: TList<TProc>;

procedure Subscribe(AHandler: TProc);
begin
eventHandlers.Add(AHandler);
end;

function Unsubscribe(AHandler: TProc): Boolean;
begin
Writeln(eventHandlers.IndexOf(AHandler)); // 0
Result := eventHandlers.Remove(AHandler) >= 0;
end;

procedure HandlerProc;
begin
Writeln('Вызов статической процедуры');
end;

var
proc: TProc;
begin
eventHandlers:= TList<TProc>.Create;

proc := procedure()
begin
Writeln('Вызов анонимной функции')
end;

Subscribe(proc); // Подписываемся анонимной функцией
Writeln(Unsubscribe(proc)); // Метод вернул True, Успех:)

Subscribe(HandlerProc); // Подписываемся не анонимной функцией
Writeln(Unsubscribe(HandlerProc)); // Метод вернет False Fail:(
FreeAndNil(eventHandlers);
readln;
end.

  Вызов Unsubscribe  в строке  Writeln(Unsubscribe(HandlerProc)); вернет False. Причина впрочем прозаична, вызов метода HandlerProc заворачивается в анонимную функцию и каждый раз новую. Потому функция, которую подписали на событие, будет отличаться от функции, которую пытаемся отписать.

  Казалось бы, простая задача — реализовать список с обработчиками и методами подписки и отписки превращается не в самую тривиальную.

  Получается, что хранить обработчики лучше как TMethod, но если все же хотим уметь подписывать универсальные обработчики (анонимные и неанонимные), тогда придется для каждого типа писать свою логику сохранения в список и вызова обработчиков. Это будут либо overload методы, либо будем использовать Rtti  и методы параметризированные типом.

  TEvent = class
events: TList<TMethod>;
class procedure Subscribe(AProc: TProc); overload;
class procedure Subscribe(AProc: TProcedure); overload;
class procedure Subscribe<T>(AProc: T); overload;
end;

  А для преобразования анонимного метода в TMethod потребуется сочинять что то вроде того, что написано в Spring4D в Springs.Events.pas:

procedure MethodReferenceToMethodPointer(const AMethodReference; const AMethodPointer);
type
TVtable = array[0..3] of Pointer;
PVtable = ^TVtable;
PPVtable = ^PVtable;
begin
// 3 is offset of Invoke, after QI, AddRef, Release
PMethod(@AMethodPointer).Code := PPVtable(AMethodReference)^^[3];
PMethod(@AMethodPointer).Data := Pointer(AMethodReference);
end;

function TEvent.Cast(const handler): TMethod;
begin
if fTypeInfo.Kind = tkInterface then
MethodReferenceToMethodPointer(handler, Result)
else
Result := PMethod(@handler)^;
end;

Читать на сайте автора.

Просто баг в коде или особенность счетчика ссылок в замыканиях

MyObject.OnEvent := procedure()
begin
MyObject.OnEvent := nil;
MyField.MyProp := 'Value';
end;
Оказывается на MyObject.OnEvent := nil; счетчик ссылок у класса замыкания скрутится в 0 и замыкание отойдет в мир иной, а на следующей строчке привет AV.

Читать на сайте автора.

Что дал переход с SVN на Git или Git как ключ для синергии хороших практик

    Год назад мы в проекте выполнили переход с SVN на Git. Нас привлекали возможности бранчевания и работы с репозиторием без сети. «Сарафанное радио» регулярно доставляло сообщения о преимуществах модели распределенных систем контроля ревизий. В итоге, переход себя полностью оправдал, и Git раскрыл множество прекрасных возможностей, о которых во время перехода даже не подозревали. Но самое замечательное, что побочным эффектом оказалось поднятие общего уровня и культуры разработки.
   
     В первой части я расскажу о своих ощущениях после перехода на Git.  Если вы уже знакомы с ним, то возможно вам следует перейти сразу ко второй части, где я попытался снизить градус слащавости и пафоса и перейти уже наконец то к делу. Во второй и третьей части я коснусь практических вопросов, которые проливают свет о влияние гита на культуру разработки. 

Дифирамбы Git’у     


    Часто пользователи SVN не видят обоснованных причин для перехода на Git (на самом деле слово Git можно смело заменить на DVCS, однако опыт у меня есть только связанный с Git, потому дальше Git будет мелькать чаще). И в действительности среди плюсов на поверхности лежит лишь концепция распределенности и мифическая легкость ветвления (бранчевания).   Как сказал мой друг, парадокс гита заключается в том, что вначале не можешь понять чем Git хорош, а потом не знаешь как это рассказать. Это надо только прочувствовать. Сухое сравнение разных концепций не всегда убедительно. Многие возможности есть в обоих VCS.  А по некоторым возможностям SVN имеет преимущества. Кто-то скажет, что у них разные области и назначения, но используют то их чаще всего одинаково. Технические аспекты лучше всего смотреть в официальном руководстве для гита https://git-scm.com/book/ru/v1 (на мой взгляд лучшее руководство по гит), поэтому дальше в статье будут ощущения и эмоции. 

     С каждым днем только крепнет уверенность, что уже ничто не заставит вернуться обратно. Сказать честно, вначале было много сомнений, стоит ли игра свеч, а не будет ли это потеря времени? Теперь смело заявляю, что Git это намного больше чем SVN, или VCS в парадигме SVN (sic!).  Система хранения ревизий с точки зрения SVN является просто хранилищем кода и его изменений, преследуя такие простые практические цели как обмен общими файлами проекта, навигация и возможность вернуться к нужной версии или найти виновника и задачу, в рамках которых был поломан продукт. Слышу от оппонентов: «мне Git не нужен, так как я работаю один/у нас нет веток/в гите нельзя работать с бинарными форматами/ гит слишком сложен/ и т.п.»  На самом деле он нужен всем (программистам, дизайнерам он может и не подходить) и вот почему.

     Git меняет представление о назначение VCS. Поднимает культуру работы с кодом проекта.  История изменения кода не менее важна, чем сам код, а может даже более.  Git предоставляет возможности выставить акцент на истории изменения кода. В системе контроля версий начинает формироваться слой документации и требований к продукту в самой непосредственной близости к коду.  И немаловажным фактором является возможность следовать новым нехитрым практикам работы с репозиторием и кодом, которые дисциплинируют разработчика. Что положительно сказывается на коде. Код становится более логичным, красивым и вылизанным.

    Почему  же в гите все это можно обеспечить? Краеугольный камень — это распределенность. Что она дает? Обычно говорят, что распределенность дает возможность работать без подключения к интернету и обмениваться ревизиями минуя центральный репозиторий. Да — это верно. Но не на это я хочу  обратить внимание. Главное, что репозиторий у вас ЛОКАЛЬНЫЙ. Вот именно так: капсом и болдом.  Это позволяет манипулировать ветками и коммитами без опасения повлиять на работу коллег. У нас есть правило, звучащее как «в репозиторий должен попадать только рабочий, целостный код, прошедший ревью». На сколько сложно это правило соблюдать в SVN, на столько легче его соблюдать в гите.
     Что же еще делать в гите легко и является ежедневной рутиной, о чем SVN джедаи могли бы только мечтать?  В Git’e легко создавать ветки, выполнять слияние, манипулировать коммитами. Вам, как заправскому шулеру, ничего не стоит  строить цепочки коммитов, дробить коммиты на более мелкие, менять коммиты местами, менять описание текста коммита и его состав.  Каждая операция требует минимального времени выполнения, никакой передачи данных по сети или копирования каталогов. 
     Ну ок. Все мы уже поняли, что Git хорош как Чак Норис в ковбойской шляпе и из хорошей задачи можно устроить отличный фарш из коммитов. Но зачем? Вот типичные моменты рабочего процесса под Git:
  • Ветки создаются под каждую задачу или просто для проверки какой-либо идеи;
  • Коммиты создаем часто, очень часто. Хотим ли особо акцентировать внимание на изменение в коде либо просто закончили итерацию, на все один ответ — коммит. В Git нет проблемы зафиксировать каждый логический блок изменений в рамках задачи отдельным коммитом. В SVN такое желание приведет к неконсистентному состоянию кода в центральном репозиторие;
  • Увлеклись, написали кучу кода, и вот у нас в одном файле изменения, которые относятся к разным логическим частям? В Git это совершенно не проблема — разные логические части одного файла Git позволяет зафиксировать разными коммитами; 
  • Забыли написать тесты и проверить их работу до написания функционала? Не проблема, пишем тесты и новый коммит вставляем перед коммитом задачи;
  • Хотим отправить изменения в центральный репозиторий, но перед этим избавиться от избытка лишней информации и вместо 15 коммитов, отправить 4 сгруппированных? Все верно — и это не проблема. 
  • Я уже не говорю про классические преимущества DVSC, такие как совместная работа над одной задачей разных членов команды. 
     Звучит как фантастика? Отнюдь — просто обычные будни работы с Git. Когда долго не работаешь с SVN забываешь, что там эти операции не возможны. Гит дает чувство пьянящей свободны, нет практически ничего, что вы не могли бы в нем делать. Все делается очень легко: манипулирование ветками и коммитами напоминает игру. И вы от это получаете удовольствие, потому что все ваши, даже сложные, задумки реализуются, а желания предугадываются.  Ну и конечно вы получается удовольствие от того, что в памяти всегда свежи ощущения от SVN.

Наши практики по работе с репозиторием кода

     Гит добавляет в методологию разработки новые аспекты, которых вовсе не ожидаешь, пока не начнешь его использовать.  Эти аспекты мы раскрывали постепенно, шаг за шагом вводя простые правила:
  
1.    На первом шаге у нас была одна постоянная ветка, бранчи использовались очень эпизодически и не было стабильной версии продукта.  Да и так бывает. Бывало даже так, что одним коммитом фиксировалось сразу несколько задач. С этого мы начинали. 
2. Потом появились ветки SVN (до SVN использовали VSS  и Perforce). SVN — хорошая система. Какое-то время использовали собственную модель бранчевания, потом сдались и перешли на стандартную раскладку trunk, tags, branches. В branches  мы создавали ветки только под большие задачи. Прошло много времени, но я хорошо помню боль и отчаянье от работы с ветками в SVN. Это было долго — долго создавать ветки, долго переключать, долго смотреть лог, а слияния веток превращались в испытание. Но с проблемами можно было мириться, если работать только с долго живущими ветками.
3. А следом появилось ревью кода. Это была практика, которая все перевернула. Сейчас мне сложно представить разработку без ревью.
4. Идеальным дополнением к ревью кода стал Git. Модель Git (DVCS) как нельзя лучше подходит для ревью кода. Следующие пункты раскроют это утверждение.
5.
Первым делом мы ввели правила написания текста коммита. Как писать коммит у нас жестко формализовано. Сами правила начали появляться еще при SVN, но только переход на Git заставил к ним относиться строго. Главное требование гласит: текст коммита обязан содержать мотивацию принятия решений и изменений, присутствующих в коде коммита. Перед выкладыванием на сервер каждая задача проходит ревью кода. Ревью кода выполняется с помощью инструмента CodeReviewer. И вот тут основной момент — ревьюверу задачи должно быть понятно все только на основе информации из коммита: непосредственно кода и текста описания коммита.  Если возникают вопросы, то описание коммита перерабатывается.  Можно найти множество плюсов подобного подхода:
  • Экономия времени. Раньше приходилось  многократно пересказывать и отвечать на одни и те же вопросы, если ревьюверов несколько. 
  • Саморевью и самоконтроль. Описание должно быть убедительное, а знания о проблеме систематизированы.  Часто бывает, то что убедительно в голове, на «бумаге» уже не столь явно. Приходилось сталкиваться с ситуацией, когда написание текста коммита приводило к полной переработке решения. Качественное описание порождает качественное решение.     «Так подожди, это же явно следствие, а не причина, необходимо установить причину», — говорит нам рациональная часть нас, «Так делать нельзя, иначе меня все равно завернут на ревью, а мое позорное описание останется на веки вечные в истории».  Однако если есть какие то условия которые толкают зафиксировать неидеальное решение, лучше их указать в тексте коммита и написать, например, что причиной скверного решения была необходимость закрыть задачу в короткий срок. В будущем люди скажут спасибо и не будут фантазировать, почему именно так было сделано, и смогут выработать наиболее верный подход к проблеме. 
  • История  коммитов теперь стала рассказывать гораздо больше и наиболее востребованную информацию. 

Раньше типичные коммиты были вот такими:

  • + мелкие исправления
  • + исправлена ошибка List index out of bounds(0) связанная с бендами
  • *refactor CreateControl method
  • *fix popup message window drawing
  • +Неблокирующее выполнение SQL-блоков. Задача 12862
  • fix: Определение паскаль операции по незакрытому тэгу <Pascal ; Удаление выборки из кэша при выгрузке библиотеки
  • + Выгрузка библиотек при снятии/установке флага использования кэша метаданны

Полный зоопарк стилей и практически полное отсутствие информативности. Чтобы понять, что же сделано в коммите, необходимо смотреть код и собирать по крупицам информацию из разных источников. Разработчики, которые заползали в такой код, могли многократно ходить по одним и тем же граблям. А про автора думать самые нелицеприятные вещи, задаваясь вопросом под какими запрещенными веществами код был написан. Ирония заключается в том, что нередко рассерженный разработчик является автором злосчастного коммита.

Теперь текст коммита стал вот таким:

«fix: исправить фокусировку на следующию ячейку после нажатия Enter #issue 40436

#Проблема 
Фокус переводился совершенно рандомно, без привязки к группировке колонок и визуальному порядку.

#Причины
В #problem-hash 234816912 в погоне за многострочными записями поломали наш механизм переход по горячим клавишам. 

#Решение
Решено задействовать встроенные в DevExpress методы перехода на следующею ячейку и отказаться от нашей логики, которая была написана для довольно старой версии DevExpress компонентов. Во-первых, в новой используемой версии девэкспресса появились вложенные банды. Похоже по истории изменений версий DevExpress и по их коду, что они и старые баги поправил, которые мы закрывали нашей реализацией. Во-вторых,  не удалось заметить, что DevExpress коллекция VisibleColumns не отсортирована, как написано в комментарии к нашему методу сортировки видимых колонок (проверялась и отсортированность колонок после перемещения их во время выполнения).Переход кнопками»вверх» «вниз» оставлен прежний, так как работает.» 

Бывает получаются и вот такого размера тексты коммита

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

В SVN это правило было тяжело соблюдать. Как правило, задача уходила на сервер одним коммитом, что бы не получить в централизованном репозитории не консистентного состояния. Описание всех изменений в одном коммите теряет свою красоту и эффективность. В git’е каждую задачу можно декомпозировать до небольших логических шагов, о чем в следующем правиле.

6.
Правила оформления задачи в ветках.
Каждую задачу, как правило, можно декомпозировать на несколько шагов. Отделив, например, рефакторинг от непосредственно кода, исправляющего ошибку. Каждый шаг — отдельный коммит в отдельной ветке задачи. В последствии вся ветка попадет в центральный репозиторий, сохраняя для поколений структурированное решение задачи. Теперь даже коммит, который был выполнен «заодно» и не являющийся непосредственно необходимым для закрытия задачи, находится в контексте задачи (в той же ветке), позволяя видеть цель и мотивацию автора.
Задача в  SVN  — это большой, размазанный по разным модулям патч.  Больших усилий стоит разобраться в таком патче. Гораздо проще, когда каждая задача — это структурное решение, и можно пробежаться по отдельным шагам. В таком решении отделяются «зерна от плевел». 

Репозиторий стал неотъемлемой частью кода проекта. 


Пример:
Пример ветки по ускорения открытия карточек  где несколько коммитов с рефакторингом и другими вспомогательными действиями предшествовали коммиту решающему поставленную задачу

По ходу ревью в код могут вносится изменения, каждое такое изменение может вносится squash коммитом. Это коммит, который в последствие с помощью команды rebase можно объединить с другим коммитом (что бы ощутить мощь Git достаточно пробежаться по оглавлению способов изменить историю https://git-scm.com/book/ru/ и http://97things.oreilly.com/wiki/index.php/Record_your_rationale).
Таким образом, можно будет и в ревью видеть развитие мысли автора. А после завершения ревью одной командой rebase можно изменить историю и получить красивое дерево коммитов, без лишнего мусора, объединив squash, fixup коммиты с базовыми.

7. Правило «юнит тесты до функциональности». Это правило говорит о том, что юнит тесты должны располагаться в дереве коммитов до исправления ошибки, которую они тестируют. В результате всегда можно проверить работу юнит теста до исправления ошибки и после. Убедиться, что тест и исправление написаны верно.

Пример:

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

Про культуру 

     Система постоянно меняется, практически невозможно постоянно актуализировать все требования к приложения и его описания. Но теперь репозиторий взял на себя новую ответственную роль. Стал рассказывать не только кто и когда написал код, но и почему(такую роль бывает берет на себя багтрекер, но это слишком далеко от кода). Можно проследить мысль автора, узнать мотивы. Это формирует еще один уровень метаинформации. С помощью такого слоя информации мы стараемся закрыть наиболее критическое место описания системы. Практика показывает, что гораздо проще восстановить предисторию и все причины принятия конкретного решения для будущей переоценки, если они привязаны к коду  и расположены прямо в репозитории. Почему важно сохранять подобного рода информацию можно почитать еще здесь 97things.oreilly.com/wiki/index.php/Challenge_assumptions_-_especially_your_own и во многих других местах.  Но при при чем здесь культура?

    Чтобы понимали, что я имею в виду под культурой, приведу отрывок из книги Бориса Евсеевича Чертока «Ракеты и Люди», за который меня наверное уже недолюбливают как друзья, так и коллеги, слишком уж часто я его вспоминаю:
«В первые годы работы над ракетной техникой практически никто из руководителей, критикующих завод, не мог конкретно сформулировать, что нужно сделать для повышения культуры производства, определить роль каждого начальника цеха, мастера и рабочего. Было слишком много общих решений. Устинов беспощадно расправлялся с начальниками цехов и производств за грязь и бескультурье. При посещениях завода он начинал с туалетов. Обычно в цехах задолго до подхода к туалету разносился характерный «аромат». В самих туалетах надо было ходить по лужам. Устинов приходил в ярость и гремел: «Какой сортир, такой и начальник цеха. Пока не добьетесь образцовой чистоты в своих сортирах, не будет чистоты и в цехах».С тех пор прошло очень много лет. Проблема чистоты общественных туалетов на наших заводах и в институтах так же, впрочем, как и в стране в целом, не решена. Это оказалось куда труднее, чем создать самое грозное ракетно-ядерное оружие и завоевать мировой приоритет в космонавтике.Явный дефицит культуры, общей производственной чистоты и гигиены до сих пор является одной из причин низкого качества многих отечественных изделий. За время войны и в последующие годы забота об элементарном комфорте в цехах, создание рабочему достойной и привлекательной общей обстановки считались излишней и непозволительной роскошью. Затраты на чистоту, комфорт, элементарный сервис с лихвой окупаются повышением производительности и качества. «

  Если театр начинается с вешалки, то проект начинается с репозитория. Отношение к репозиторию закладывает отношение ко всему проекту. Аккуратность, формализованность, структурированность, обоснованность, ответственность и прозрачность  — качества, которые культивируются приведенными выше правилами работы с репозиторием, переносятся и на другие аспекты проекта. Сам workflow выполнения задачи сопротивляется поспешным, не продуманным, ошибочным и не обоснованным решениям. Код должен выглядеть хорошо и аккуратно не только внутри, но и снаружи. Мелочей не бывает. При переходе на Git о таких вещах, к сожалению, почти не задумывались. Git конечно, не серебряная пуля и не Святой Грааль, но крайне эффективный инструмент, который может сильно изменить ландшафт методологии разработки.

    Гит раскрывает свои возможности постепенно. Постоянно открываем что-то новое. В начале используется базовый набор команд. Но потом постепенно открываются все новые и новые возможности и пути оптимизации операций и повышения собственной эффективности. Кажется, что мы используем от 20 — 40% возможностей Git. Мы продолжаем познавать Git, постоянно экспериментируем и дорабатываем наши правила и стандарты. Возможно читатель поделится и своими успешными практиками. 

Несколько полезных ссылок

    Некоторые правила я не описал, а некоторые из приведенных правил у нас формализованы и описаны гораздо подробнее. Многие правила для подробного описания потребуют статью. Наибольшую важность несет в себе ревью и стандарт написания текста коммита.  Крайне рекомендую ресурсы, на основе которых у нас формировались правила, касающиеся написания текста коммита:

Читать на сайте автора.

Об альтернативе Application.ProcessMessages для TWebBrowser и разрыве стека выполнения

  При использовании TWebBrowser существуют две неприятности, во-первых, это сам TWebBrowser =), а во-вторых, это Application.ProcessMessages, который необходимо выполнять чуть ли не на каждое действие (загрузить документ, сменить режим редактирования и т.п).
  Использование Application.ProcessMessages может вызывать неожиданные проблемы. Особенно это актуально, когда используются оконные windows сообщения для разрыва стека выполнения. Но нашелся способ, которое позволяет не выбирать все сообщения из очереди. На первый взгляд, решение даже работает, однако буду рекомендовать никогда его не использовать и всегда использовать Application.ProcessMessage и об этом ниже.
  Очевидно, что WebBrowser обрабатывает какие-то сообщения. Но поиск их казался гиблой идеей.   В действительности оказалось, что ни так все плохо. С помощью Window Detective было обнаружено, что в момент загрузки происходит подозрительная посылка сообщений окну с именем класса  ‘Internet Explorer_Hidden’. Решил проверить и выбрал из очереди оконных сообщений в момент загрузки документа сообщения предназначенные только этому окну. К моему удивлению — все заработало.
Сообщения получаемые окном IE
Вообщем вот заготовка кода:
TMyWebBrowser = class(TWebBrowser)
protected
procedure WBProcessMessage;
procedure InternalSetValue(const AValue: string);
public
procedure SetValue(const AValue: string); // Входная точка в примера
procedure WaitWB;
end;


function EnumWindowsToFindIEHiddenProc(AHandle: HWND; AParam:NativeInt): boolean; stdcall;

var
IEHiddenHandle: Hwnd;

implementation

procedure TMyWebBrowser.SetValue(const AValue: string);
begin
InternalSetValue(AValue); // выполняем каким либо способом присваивание разметки
WaitWB; // Ждем завершение загрузки документа.
FooFunction; //Какой то функционал для работы которого необходим полностью загруженные html документ.
end;

procedure TMyWebBrowser.WaitWB;
begin
//Как то так обычно выглядит ожидание пока документ полностью не загрузиться
while HTMLDocument2.readyState <> 'complete' do
begin
WBProcessMessage; // выбираем только нужные сообщения
//Forms.Application.ProcessMessages; // выбираем все сообщения из очереди
end;
end;


procedure TbtkHTMLEditor.WBProcessMessage;
var
msg: Windows.tagMSG;
processID : THandle;
begin
IEHiddenHandle := 0;
processID := GetCurrentProcessId;
if EnumWindows(@EnumWindowsToFindIEHiddenProc, processID) then // �щем хендл окна IE в нашем процессе перебирая все окна
if IEHiddenHandle <> 0 then // Проверяем найденный хендл валидный
if PeekMessage(msg, IEHiddenHandle, 0, 0, PM_REMOVE) then // извлекаем из очереди оконных сообщений все сообщения для окна IEHiddenHandle
begin
Windows.DispatchMessage(msg); // Передаем извлеченные сообщения окну IE
end;
end;

function EnumWindowsToFindIEHiddenProc(AHandle: HWND; AParam:NativeInt): boolean;
var
processId: NativeInt;
classbuf: array[0..255] of Char;
const
IEWndClassName = 'Internet Explorer_Hidden';
begin
result := true;
if Windows.GetWindowThreadProcessId(AHandle,@processId) <> 0 then
begin
if AParam = processId then
begin
GetClassName(AHandle, classbuf, SizeOf(classbuf));
if lstrcmp(@classbuf[0], @IEWndClassName[1]) = 0 then
begin
IEHiddenHandle := AHandle;
result := false;
end;
end;
end;

end;
  Код на скорую руку, проверялся с IE 9-10 в Windows 7-8. Но в данном случае я избегаю слова «решение», тут больше подходит — «грязный хак». На самом деле, если у вас есть проблема того, что внезапный ProcessMessages нарушает строгий порядок ваших вызовов, то проблема не в ProcessMessages. ProcessMessages это данность архитектуры Windows и VCL. Если посылаете оконное сообщение, то будьте готовы, что оно может быть извлечено раньше, чем вы предполагаете. Например, из окна посылаем себе же сообщение WM_Close. Сообщение извлекается вот таким неожиданным ProcessMessages еще до раскрутки стека выполнения, который вызвал посылку этого сообщения. В результате дальнейшая раскрутка стека выполнения пойдет по освобожденным объектам, так как на WM_Close будет убито родительское окно и все дети на нем.
  Подобные проблемы не решить изъятием вызовов ProcessMessages. Любой модальный диалог нарушит подобный не очень хитрый план. Для избегания подобной проблемы нужно использовать другие подходы. Мы, например, подобные проблемы решаем так называемыми контекстами асинхронного выполнения и подсистемой асинхронных команд. Говоря «асинхронные команды», я не подразумеваю много-поточное выполнение, а только разрыв стека выполнения. Что и происходит, когда посылаем в свой же поток оконное сообщение.
Вся суть в том, что асинхронные команды — это надстройка над механизмом оконных сообщений, позволяющая передавать в качестве сообщений объекты, а контекст — это состояние системы, определяющее возможность исполнения этого объекта.
Таким образом, асинхронная команда — это объект, который посылается в очередь сообщений, сообщением  WM_AsyncCommand, где в качестве параметра указатель на объект. При извлечении сообщения WM_AsyncCommand из очереди сообщений, у объекта асинхронной команды вызывается обработчик, который и исполняет полезный код. Асинхронная команда принадлежит контексту. Контекст создается при запуске приложения. Если на момент извлечения асинхронной команды из очереди сообщений контекст заблокирован, тогда асинхронная команда перемещается в особый буфер команд, где дожидается разблокировки контекста.  Все что осталось, это заблокировать контекст на момент начала роста стека вызовов, в котором могут посылаться команды, имеющие возможность порушить стек выполнения:

Context.Lock;
try
    AnyUserAction;
finally
    context.Unlock;
end;

Выше я приводил пример с WM_Close. Для этого мы используем TFormCloseAsyncCommand = class(TAsyncCommand). Ко всему прочему, контексты могут быть вложенными, а команды имеют фьючеры для возможности их отмены. Но это явно не вопрос темы текущей статьи (если кому интересно, то могу накидать шаблон кода и варианты использования в отдельной статье).
Вернемся к «грязному обходному пути» и оставим в стороне вопрос о том, что ProcessMessages — данность и не является безусловным злом. И тем не менее выборочная диспетчеризация сообщений для класса «Internet Explorer_Hidden» —  сомнительный путь:
1. Данный вопрос никак не документирован. В MSDN мне попадался только код вида while (browser.IsBusy){System.Windows.Forms.Application.DoEvents();} .
2. «Решение» не проверялось на всех возможных версиях IE и Windows.
3. Нет гарантии, что мы обрабатываем все нужные сообщения.
4. В будущем архитектура WebBrowser может измениться и данный подход уже может  не работать.
5. Извлекая только определенные сообщения, мы можем нарушить порядок обработки сообщений

Читать на сайте автора.

Жизнь и восстановление после StackOverflow

Распространено мнение, что StackOverlow — это приговор. Все, что остается приложению — это красиво схлопнуться, не оставив возможности пользователю продолжить работу, сохранить важные данные, и даже информацию об ошибке.

Дальше я расскажу, зачем и как можно обрабатывать ошибку переполнения стека, а так же какую «свинью» подложила EurekaLog — инструмент, который должен был обеспечивать нас информацией о том, почему клиент снова в бешенстве.

Сначала немного справочной информации о том, как устроен стек.

Устройство стека 

В каждом потоке под стек  резервируется по умолчанию 1 МБ адресного пространства. Зарезервированная память физически еще не выделена. Все адресное пространство разбито на страницы — минимальные блоки, которыми оперирует системный менеджер памяти. Стек растет от большего адреса к меньшему постранично. Память выделяется (становится commited) в момент роста стека. Для того, что бы определить необходимость выделения еще одной страницы из зарезервированных, используется защищенная страница памяти. Это такая страница, которая уже выделена (закомичена)  и помечена флагом Page_Guard. Защищенная страница всегда располагается следом за страницей, в которой находится текущая вершина стека. При росте стека происходит обращение к защищенной странице, система реагирует, снимается флаг Page_Guard со страницы и возбуждается исключение доступа к защищенной странице.  Обработав исключение система понимает, что необходимо выделить еще страницу памяти из зарезервированного пространства и пометить ее флагом Page_Guard. Уже выделенная память стека обратно не возвращается, и страницы остаются commited даже после полной раскрутки стека.
Последняя (в сторону роста стека) зарезервированная страница адресного пространства никогда не выделяется. Она служит буфером, что бы вершина стека не вышла за границы сегмента.

Механизм возбуждения StackOverflow 

В какой-то момент в процессе роста стека остается только одна защищенная страница перед последней зарезервированной. Приложение пытается нарастить стек. С защищенной страницы снимается флаг защиты и происходит попытка выделить еще одну защищенную страницу. Система не разрешает и возбуждает исключение переполнения стека (StackOverflow). Как правило, после этого у нас остается еще почти целая страница для обработки исключения.
С этого времени система снимает с себя ответственность за контроль стека. Page_Guard  в дальнейшем больше не выставляется страницам, и если стек снова достигнет предела, произойдет обращение к последней зарезервированной странице. В результате система убъет поток и приложение. Для пользователя — приложение просто схлопнется.

Почему StackOverflow это проблема

Почему же, зачастую пользователь не видит даже сообщения о переполнении стека? Вместо этого приложение просто исчезает. Все дело в том, что оставшиеся 4 килобайта памяти для обработки исключения это очень мало. В rtl много подобных мест:

procedure FmtStr(var Result: string; const Format: string;
const Args: array of const; const FormatSettings: TFormatSettings);
var
Len, BufLen: Integer;
Buffer: array[0..4095] of Char;
begin
..
end;

Лишь один такой вызов израсходует всю оставшуюся доступную память для того, что бы расположить на стеке массив Buffer. 
В рабочем проекте мы используем EurekaLog для сбора информации о стеке исключений и формирования отчета об ошибке. Но при обработке исключения StackOverflow, эврика обращалась к методам rtl, которые не оставляли нам шанса узнать причину, почему у клиентов на проекте наш продукт в момент работы схлопывается.
Была еще одна причина, которая побудила решить эту проблему кардинально. Большую часть нашего продукта занимает скриптовый движок для исполнения паскаль скриптов. Клиентская логика пишется на паскаль скрипте. Паскаль скрипт динамически подгружается. Таким образом, если программист допустит ошибку при написание скрипта и получит StackOverflow, то не останется другого варианта как снова перезапустить развесистый фреймворк.

Обработка StackOverflow

В MSDN есть функция
  BOOL WINAPI SetThreadStackGuarantee(_Inout_  PULONG StackSizeInBytes);
с помощью которой можно установить размер памяти, который останется доступным после возбуждение StackOverflow для обработки исключения. Функция выполняет простую вещь. Если с помощью ее установить гарантированный размер 8 килобайт, то в этом случае система будет держать перед вершиной стека 2 защищенные страницы по 4 килобайта.  
Теперь мы можем гарантированно собрать информацию об ошибке. Эвристическим путем было выяснено, что для эврики надо 32 кБ. Для перестраховки мы задаем 64кб. Однако следующее переполнение стека будет фатальным. 
Но для полного восстановления после StackOverflow необходимо вернуть защищенные страницы. Когда это сделать? Когда стек свернется, и вершина стека уйдет со страниц, на которых мы хотим восстановить Page_Guard. При этом остается вероятность, что при обработке исключения переполнения стека, мы получим новое переполнение стека. Для этого сразу же после первого переполнения стека выставим на одной верхней странице флаг Page_Guard.  И в случае нового переполнения стека при обработке предыдущего, мы сможем хотя бы сообщить о переполнение стека при обработке переполнения стека и закрыть приложение.
Вот так это выглядит в теории:
На практике будут следующие шаги:
  1. Установить гарантированный размер для обработки стека равный 68 кБ;
  2. Необходимо повесить хук на возникновение исключения. В случае StackOverflow начать процесс восстановления. Установить на 1 страницу Page_Guard. В Delphi можно повесить для этих целей обработчик на System.ExceptObjProc;
  3. Дождаться раскрутки стека. Это самый интересный момент. Тут возможны разные варианты. Один из самых простых вариантов —  установка хука на оконные сообщения и на onIdle. В VCL приложение стек как правило всегда раскручивается до Application.ProcessMessage. Вот тут и попытаемся восстановить остальные 16 защищенных страниц;
  4. Если в момент восстановления стека, система возбудила новое исключение StackOverflow, то ничего не остается, как сделать хорошую мину: информируем пользователя об исключении и выполняем перезапуск приложения.
Приведу простейший пример восстановления после StackOverflow. Все начинается с Button1Click и секция initialization:
unit main;

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;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
procedure DoOnMessage(var Msg: TMsg; var Handled: Boolean);
end;

function SetThreadStackGuarantee(var StackSizeInBytes:
Cardinal): LongBool; stdcall; external 'kernel32.dll' name 'SetThreadStackGuarantee';
type
proc = procedure();
TRaiseExceptionProc = procedure(ExceptionCode, ExceptionFlags: LongWord;
NumberOfArguments: LongWord; Args: Pointer); stdcall;
TExceptObjProc = function(P: PExceptionRecord): Pointer;
var
Form1: TForm1;
oldRaiseExceptionProc: TExceptObjProc = nil;
restoringAfterStackOverlow: boolean = false;
stackGuaranteeSize: Cardinal;
const
PageSize = 4096;

function _RaiseExeceptionProc(P: PExceptionRecord): Pointer{Exception};
implementation

{$R *.dfm}

{$POINTERMATH ON}

function GetSegmentStackTop: Pointer; assembler;
type
NT_TIB32 = packed record
ExceptionList: DWORD;
StackBase: DWORD;
StackLimit: DWORD;
SubSystemTib: DWORD;
case Integer of
0 : (
FiberData: DWORD;
ArbitraryUserPointer: DWORD;
Self: DWORD;
);
1 : (
Version: DWORD;
);
end;
asm
MOV EAX, FS:[0].NT_TIB32.StackLimit
end;

function GetESP: Pointer; assembler;
asm
mov result, esp
end;

function GetMemoryInfo(addr: NativeUInt): TMemoryBasicInformation; inline;
begin
VirtualQuery(Pointer(addr), Result, SizeOf(Result));
end;

procedure TerminateRecoveringAfterStackOverflow;
begin
Assert(restoringAfterStackOverlow);
MessageBox(0, 'Не удалось восстановится после StackOverflow, приложение будет закрыто.',
'Все пропало',
MB_ICONERROR);
Halt(0);
end;

function RestoreStackPageGuard(APageAddress: Pointer): boolean;
var
oldProtectFlag: dword;
begin
result := VirtualProtect(APageAddress, PageSize, PAGE_READWRITE or PAGE_GUARD, oldProtectFlag);
end;

procedure StartRecoveringAfterStackOverflow;
begin
Assert(not restoringAfterStackOverlow);
restoringAfterStackOverlow := true;
RestoreStackPageGuard(GetSegmentStackTop);
Application.OnMessage := Form1.DoOnMessage;
end;

procedure StopRecoveringAfterStackOverflow;
begin
Assert(restoringAfterStackOverlow);
restoringAfterStackOverlow := false;
Application.OnMessage := nil; // Так конечно лучше не делать, а использовать для этих
// целей вместо обработчика OnMessage хук на сообщения
end;

procedure ValidateStackPageGuards;
var
memoryInfo: TMemoryBasicInformation;
address, stackLimit: NativeUInt;
guarded: Boolean;
begin
address := NativeUInt(GetESP);

guarded := False;
address := NativeUInt(GetMemoryInfo(address).BaseAddress);
stackLimit := NativeUInt(GetSegmentStackTop);


while not(guarded) and (address >= stackLimit) do
begin
memoryInfo := GetMemoryInfo(address);
guarded := (memoryInfo.Protect and PAGE_GUARD) = PAGE_GUARD;
address := address - PageSize;
end;

if not guarded then
begin
TerminateRecoveringAfterStackOverflow;
end;
end;


function _RaiseExeceptionProc(P: PExceptionRecord): Pointer{Exception};
begin
if (GetCurrentThreadId = MainThreadID) then
begin
if not restoringAfterStackOverlow then
begin
if Assigned(P) and (P.ExceptionCode = STATUS_STACK_OVERFLOW) then
begin
StartRecoveringAfterStackOverflow();
end
end else
ValidateStackPageGuards;
end;

if Assigned(oldRaiseExceptionProc) then
result := oldRaiseExceptionProc(P);
end;



procedure WeNeedToGoDeeper(var i : integer);
var
a:array[0..4000] of char;
begin
inc(i);
WeNeedToGoDeeper(i);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
i := 0;
try
WeNeedToGoDeeper(i);
finally
MessageBox(0, PChar(Format('На %d вложение',[i])), 'Ошибка StackOverflow', mb_ok);
end;
end;

procedure TForm1.DoOnMessage(var Msg: TMsg; var Handled: Boolean);
var
i: integer;
begin
Assert(restoringAfterStackOverlow);
ValidateStackPageGuards;

for i := 1 to 16 do
begin
if not RestoreStackPageGuard(Pointer(NativeInt(GetSegmentStackTop) + PageSize * i)) then
begin
TerminateRecoveringAfterStackOverflow();
end;
end;
StopRecoveringAfterStackOverflow();
end;

initialization
stackGuaranteeSize := PageSize * 16;
SetThreadStackGuarantee(stackGuaranteeSize);

oldRaiseExceptionProc := ExceptObjProc;
ExceptObjProc := @_RaiseExeceptionProc;
end.

Проверить, что восстановление после StackOverflow работает можно с помощью утилиты  vmmap из SysInternalsSuite.

Первый скриншот после запуска приложения. 16 кБ используется стеком из 1МБ. 68 кБ защищенная область.

Сразу после StackOverflow. Вся память выделена.
После восстановления работоспособности.

Более подробно о работе стека можно почитать тут:

  1. MSDN http://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686774(v=vs.85).aspx
  2. У Джефри Рихтера в книге «Создание эффективных WIN32-приложений»
  3. У Марка Руссиновича в блоге http://blogs.technet.com/b/markrussinovich/archive/2009/07/08/3261309.aspx и книге «Внутреннее устройство Microsoft Windows»

Читать на сайте автора.

Немного про замыкания

Наверное для тех кто постоянно использует замыкания не будет новостью. Но вот осознал, что может существовать только одно замыкания для какого то конкретного контекста. То есть, анонимных функций может быть несколько, но анонимный класс для контекста создается один.
В этом легко убедиться:

program closuretest;

{$APPTYPE CONSOLE}

{$R *.res}

uses
System.SysUtils;

procedure Test;
var
i: Integer;
m: string;
proc, proc2: TProc;

function GetClosure1:TProc;
begin
Result := proc;
end;

function GetClosure2:TProc;
begin
Result := proc2;
end;

var
intf1, intf2: IInterface;
begin
i := 0;
proc := procedure()
begin
inc(i,2);
Writeln(i);
end;
proc2 := procedure()
begin
inc(i,3);
Writeln(i);
end;

proc();
proc2();

GetClosure1.QueryInterface(IInterface, intf1);
GetClosure2.QueryInterface(IInterface, intf2);
Writeln(GetClosure1 = GetClosure2); //False
Writeln(intf1 = intf2); // TRUE, что и требовалось доказать

Read(m);
end;

begin
try
Test;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

Читать на сайте автора.

Delphi Event bus

Решились на работе поддержать Delphi сообщество и начать выкладывать в open source собственные наработки. 

Первой ласточкой суждено было стать пакету DelphiEventBus — реализация паттерна проектирования Event Bus.  
В Jаva мире есть такие пакеты как guava-libraries, но в Delphi ничего похожего найти не удалось. Потому решено было запилить нечто подобное. 

Из статьи Java event bus library comparison можно выцепить характеристики библиотек реализующих шину сообщений. 

Для DelphiEventBus получается следующие:

  • Объявление слушателя — аннотация

  • Синхронность отправки в шину — по умолчанию отправка синхронна. 

  • Асинхронность — в планах на будующее

  • Фильтрация событий — статическая, т.е. у обработчиков в листенера можно задать декларативно фильтры и их значения. При посылки события задаются значения предопределенных фильтров для конкретного события

  • Иерархия событий — да. Событие это объект. Есть базовый класс всех событий. Обработчик может ждать события определенного класса и всех его наследников

  • Строгость ссылочности листенера — строгая. Обязательная дерегистрация. Регистрируются и дерегистрируются  сразу все обработчики в листенере

  • Приоритет обработчиков — отсутствует. Но дополнительно реализован механизм хуков, похожий на виндовый механизм.

  • Реализованы хуки — возможность игнорировать фильтры и выстраивать цепочки обработки. Вызов хуков строго обратен порядку регистрации.

Должно работать на XE3 и выше. Еще бы readme перевести на английский…


Читать на сайте автора.

Для расовых ненавистников "with"

Коим я являюсь (ненавистником with)

Добрые люди предложили замену:

(procedure (A: TObject)

  begin
    A.Free;
  end)(TObject.Create);

Читать на сайте автора.