Привет всем !
Возникла проблема: не выполняется конструкция try..except в dll'ке
- т.е. прога просто говорит runerror и слетает.
В руководстве по fpc ничего о таком исключении не говориться,
я даже поначалу подумал, что это ошибка в rtl'е и написал bugrep
(http://www.freepascal.org/bugs/showrec.php3?ID=5067).
Но в ответ получил нечто не вполне понятное: 'это не ошибка' и 'см
br # 2573'. Но в br # 2573 стоит статус "Unfixed" ("не исправлено" или "не исправимо" ?). Этому BR уже три года.
Я решил подумать еще раз и покопаться в исходниках rtl. Дальнейшее
- мои соображения. Поправьте меня, пожалуйста, если я в чем-то ошибся.
Dll - это набор процедур, которые могут быть вызваны из основной программы
или другой dll'ки. Они исполняются в потоке основной программы,
следовательно любые нештатные ситуации (исключения, прерывания)
в них обрабатываются одинакого для всех dll данной программы, а также
и для процедур данной программы. Т.е., например, если в dll'ке или
основной программе возникает деление на 0, операционка должна
послать что-то вроде SIGFPE и либо снять программу либо вызвать
зарегистрированный программой обрабочик сигналов.
Обработчик для конкретного
сигнала в данный момент времени может быть только один. Если программа
написана полностью на pascal'e и не использует dll'ек, обработчик
регистрируется модулем system, в нем происходит либо возврат longjmp'ом
на except (если ошибка происходит внутри try), либо изучается exitproc
и либо происходит переход на exitproc либо выводится сообщение runerror.
Здесь вроде все понятно.
Если же код компилируется в dll'ку, компилятор, вообще говоря, не знает,
кем и как она будет вызвана. Однако руководство по компилятору и практика
показывает, что в dll'ке можно обращаться ко всем процедурам модуля
system (writeln, например), вероятно, можно использовать ansistring
и прочие прелести. Вместе с тем, dll'ку можно вызвать хоть из
полуторакилобайтной программы, сгенерированной flat assemblerом - в
которой гарантированно нет процедур, подобных writeln. Отсюда следует
вывод: в dll'ку встраивается весь необходимый код модуля system
и любых других модулей, которые будут упомянуты в uses.
Теперь предполагаем простой вариант: в программе используется две
dll'ки, сгенерированные fpc. Каждая из них содержит свой экземпляр
модуля system, со всеми преодпределенными константами, распределенной
памятью под переменные... И вот что любопытно: каждая копия system
функционирует, фактически, независимо от другой ! И к тому же
- независимо от той копии, что сидит в основной программе - exeшнике.
Т.е. переопределение exitproc, например, внутри dll'ки вообще
ни на чем не отразиться: при возникновении div by zero будет
вызван обработчик, зарегистрированный exe'шником. Процедура Halt
так же будет себя вести различно, в зависимости места своего
вызова - каждый модуль (dll и exe) будут вызывать свои цепочки exitproc.
Таким образом попытка вставить try .. except внутри dll'ки
может отлавливать лишь ошибки, которые диагностирует сам модуль
system (например, ошибка открытия файла), но любые ошибки,
связанные с вмешательством ОС (деление на ноль, GPF..)
будут вызывать обработчик основной программы, который просто не имеет
понятия о try..excepах, exitproc'ах и прочем, имеющемся в dll'ке
(у двух копий system просто нет никакого интерфейса, чтобы договиться
о взаимодействии).
Если же, например, dll'ка вызывается exeшником, постороенным
не fpc, а, например, C'шным компилятором - он может вообще не
не встраивать никакого обработчика на SIGSEGV, SIGFPE...
Т.е. правильно ли я понимаю, что try..except просто технически
не может обработать ошибки арифметики и это не ошибка rtl-fpc,
а просто не упомянутая в документации особенность ?
Это во первых. А во вторых: ошибка, не приводящая к вмешательсту
ОС, возникшая внутри dll, может быть просто "не замечена" основной программой ?
Т.е. процедура, находящаяся в dll, например,
попытавшаяся открыть для чтения
несуществующий файл будет просто снята с выполнения (но без выполнения
_exit() - почему ?), что приведет
к чему угодно. У меня, например, основная прога при этом вылетала
по runerror(216), а не runerror(2).
Причем это одинакого верно как для linux'ов/BSD так и для win ?
Что будет, если попробовать грязный хак - попытаться из раздела
инициализации dll'ки вызвать раздел инициализации модуля system
(своей копии),
чтобы перевести стрелки обработчиков на себя ? Это собъет с толку
копию system основной программы, конечно, но все таки - возможно
ли это теоретически и как ?
Собственно все началось с простой, на первый взгляд, проблемы:
есть прога на C, которая
рисует окно и выводит результаты математической обработки,
которая выполяется fpcшной dll'кой. Нужно сделать так, чтобы
при возникновении разнообразных арифметических проблем
в dll'ке
выполнялись freemem/dispose и управление возвращалось Cшной проге:
дать возможность пользователю задать новые параметры расчета.
Конечно, можно при выделении памяти составлять табличку: что
конкретно выделено, а потом, при аварии, которая будет обработана
Cшным кодом, вызывать из dll'ки специальную процедуру освобождения,
но try..except был бы красивее....
Спасибо заранее