Компонентная библиотека JCL
Компонентная библиотека JCL содержит много, хоть и не визуальных, но тем не менее очень полезных объектов. Кроме того, она содержит ряд дополнений к IDE.
В область моих интересов она попала в первую очередь из-за наличия возможности трассировки стека исключений. Это, фактически, самая развитая "фишка" этой
библиотеки.
- Необычно наличие инсталлятора. У библиотеки достаточно много опций, которые хоть и можно менять после установки, но все же некоторые предпочтения можно
настроить сразу. Если установлено несколько версий IDE, то под каждую можно сделать свои опции. Все бинарники собираются раскладываются по папкам. Библиотеки
и пути регистрируются инсталлятором. Вобщем приятно :).
- Отладочная информация для анализа стека ошибок может храниться в разных форматах, например TD32 или map-файл. Но самым "продвинутым", разумеется является
"свой" формат jdbg, который намного компактнее. Оформляется в виде отдельного jdbg-файл, либо в виде ресурса. В IDE входит эксперт, который позволяет сгенерировать
все при компиляции (автоматически предлагает сформировать детальный map-файл, который и используется для формирования jdbg). В комплект входит так же командная утилита
для формирования файла/ресурса из map-файла (можно использовать в различных make-скриптах).
- Для меня оказалось удивительным, что jdbg формат не сжат - последующее сжатие уменьшает размер еще раза в 2, что очень приятно.
- Эксперт jcl может добавлять в проект комментарии специального вида, которые фактически являются директивами для эксперта.
JCL и компонентные ActiveX приложения
Для того, чтобы трассировать ошибки модули JCL перехватывают системные вызовы, в частности Kernel32.RaiseException в таблице импорта и System.ExceptObjProc. Первая нужна
для первехвата любой raise конструкции, а вторая для перехвата системных ошибок типа divizion by zero или access violation. В случае, если приложение включает в себя
ActiveX/dll библиотеки (и bpl-пакеты полагаю тоже), то эти переменные и таблицы импорта могут быть разные и исключения будут перехватываться только в определенной "зоне видимости" -
коде, который использует вызовы перехваченные JCL.
Проблему с перехватом исключений из библиотек можно решить средствами JCL. Для этого все проекты надо перекомпилировать с использованием JCL, а в инициализации библиотек должен
быть прописан вызов JclInitializeLibrariesHookExcept, который позволит каждой библиотеки индивидуально перехватывать исключения. Кроме того JCL должна собираться с определением
HOOK_DLL_EXCEPTIONS, иначе перехвата не будет.
Однако, если есть возможность использовать стандартный пакет VCL, то можно попробовать обойтись обработкой исключений JCL только в исполняемых файлах. Если все библиотеки так же будут
использовать пакет VCL, то код вызова и обработки исключений модуля System будет общий и только за счет этого "зона видимости" для JCL расширится. Но все же определенные проблемы в
обработке COM/ActiveX исключений останутся.
JCL и reraise ошибок
Трассировка исключений JCL, конечно, помогает локализовать ошибки, однако, обработка ошибок, существующая в приложении может серьезно ухудшить результаты трассировки. Так, достаточно
часто используют "перевозбуждение" ошибки, которое сводится по сути к созданию новой исключительной ситуации:
try
...
except
on E: Exception do
raise Exception.Create('There was an error: ' + E.Message);
end
С одной стороны - типичный код, с другой - это 2 независимых исключения, причем, возможно с разными типами. Если наша задача не сводится к ловле всех скрытых программой исключений (что то же
можно, но их зачастую очень много), то мы получим последнее созданное исключение и его стек. Те часть информации об истории возникновения ошибки точно потеряем.
Мне кажется более привлекательным сохранить объект-исключение:
try
...
except
on E: Exception do begin
E.Message := 'There was an error: ' + E.Message;
raise;
end;
end
Однако следует понимать, что за сообщение в конечном итоге отвечает конкретный класс-исключение. Например, для системного исключения подобный код не изменит сообщения.
JCL и safecall вызовы
Особый интерес представляет обработка исключений в safecall вызовах.
- Safecall вызов всегда приводит к пересозданию исключения вызывающей стороной, как описано в предыдущем пункте. Те история возникновения исключения будет видна только
от вызова (_CheckAutoResult), что конечно совсем не интересно.
- Safecall вызовы не переваривают системные ошибки, выдавая одно сообщение на все типы (Разрушительный сбой). Что для меня вообще является непонятным - получается
преднамеренная маскировка информации об ошибке.
- И наконец, со стороны реализации safecall интерфейса обработка ведется не совсем обычно (хотя предыдущий пункт вероятно является следствием этого), в качестве обработчика
ошибок выступает _HandleAutoException, который вовсе не учитывает System.ExceptObjProc, а, следовательно, и JCL не может учесть системной ошибки в этом вызове. Самый простой
способ заставить exception обработаться штатно в этом случае - написать try except блок перевозбуждающий ошибку (но не заменяющий объект-ошибку).
Для того, чтобы компенсировать укорочение истории исключение, можно попробовать вмешаться в обработку safecall вызова написав собственный обработчик SafeCallErrorProc. Я написал
такой обработчик, добавляющий исключения типа EOleException в игнорируемые JCL на время вызова. При его использовании стек исключения для вызова "удлинился" и включил вызовы
модуля, в котором находилась реализация safecall-интерфейса. Однако такой подход может привести и к проблемам, если перед обработкой были скрытые исключения,
стек которых может быть "втянут" в результат. Так же он не решает проблемы с системными исключениями.
Исходный текст JclHookSafecall.zip (1кб)
21.06.2009