Компонентная библиотека JCL

Компонентная библиотека JCL содержит много, хоть и не визуальных, но тем не менее очень полезных объектов. Кроме того, она содержит ряд дополнений к IDE. В область моих интересов она попала в первую очередь из-за наличия возможности трассировки стека исключений. Это, фактически, самая развитая "фишка" этой библиотеки.

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 вызова написав собственный обработчик SafeCallErrorProc. Я написал такой обработчик, добавляющий исключения типа EOleException в игнорируемые JCL на время вызова. При его использовании стек исключения для вызова "удлинился" и включил вызовы модуля, в котором находилась реализация safecall-интерфейса. Однако такой подход может привести и к проблемам, если перед обработкой были скрытые исключения, стек которых может быть "втянут" в результат. Так же он не решает проблемы с системными исключениями.

Исходный текст JclHookSafecall.zip (1кб)

21.06.2009

Hosted by uCoz