| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > Delphi: Для новичков > Sender и Exception |
| Автор: Avers 10.11.2008, 11:20 | ||||
| Не могу разобраься, какой объект вызвал ошибку, а какой метод (в котором эта ошибка возникла) Простой пример: бросаем на форму кнопку и пишем код
Как бы кнопку не нажимали, в сообщении будет TButton А если написать свой обработчик исключений))... начинается самое интересное
При фокусировке на кнопке жмем энтер и тоже получаем TButton. Но вот если нажать кнопку мышью, то получим TForm1 (!!!!) Почему? |
| Автор: CodeMonkey 10.11.2008, 11:51 |
| Вообще-то метод, в котором возникло исключение, стандартными средствами не отслеживается никак. Стандартный Application.HandleMessage всегда вызывается с Sender = форме, в методах которой расположен его вызов, а если он вызывается вне формы - то в Sender передаётся Application. Если вас интересует диагностика места возникновения ошибки - см., например, http://www.delphikingdom.ru/asp/answer.asp?IDAnswer=57687 |
| Автор: Avers 10.11.2008, 12:01 |
| В данный момент мне хотелось бы узнать, почему MyException передатся один раз TButton, другой раз TForm1 То, что нельзя легко и просто узнать метод, вызвавший ошибку, я смирюсь. Но хочется хотя бы знать объект и/или модуль, в котором ошибка возникла. |
| Автор: Avers 11.11.2008, 10:04 |
| я неверно выразился - мне нужно имя класса. Т.е., если бы не было этой канители то, все было бы замечательно: по Sender можно было определить класс от которого пришло исключение, а следовательно и модуль и т.п. и во многих (пусть не всех, но во многих) случаях метод, в котором произошло исключение... (а мне больше и н надо). Но не зная класса.... все остальное становится бессмыслено. Как же тогда определить модуль, в котором произошло исключение? |
| Автор: CodeMonkey 11.11.2008, 10:39 | ||
Ну вот поскольку Sender равен тому, кто поймал сообщение, а часть сообщений для кнопки отправляются именно форме, то так вот запросто это сделать не получится. 1. В принципе, на руках есть ExceptAddr - точный адрес инструкции, возбудившей исключение. У нас на руках есть и RTTI - в частности, список имён published-методов (в том числе обработчиков событий) и их адресов. Проблема состоит в том, как определить, какому методу принадлежит ExceptAddr. Имеется ввиду, что начало каждого метода известно, а вот сколько он занимает байт в длинну - нет. Может здесь и можно что-то придумать, но мне ничего в голову не приходит. 2. Простейшее в реализации будет подключить JclDebug, включить в опциях генерацию map-файлов и вызвать функцию GetLocationInfo. Простейшее в том смысле, что уже всё готово и написано. 3. Ну и, конечно, как вариант можно рассмотреть применение грязных хаков - например, подмена функций и методов перенаправления сообщения. В частности, обернуть Control.Perform в try ... except Application.HandleException(Control); end; Тогда бы Sender указывал на того, кого вы хотите. Но, помимо того, что это очень грязно, проблема ещё и в том, что некоторые нужные нам функции (например, тот же DoControlMsg) находятся в implementation-секциях модулей и не имеют внешних ссылок. Т.е. неизвестен их адрес. Аналогично - готового решения здесь мне не видно. Добавлено через 1 минуту и 54 секунды ...хотя... с другой стороны, сам Perform ведь в интерфейсной части, можно попробовать подменить его... |
| Автор: CodeMonkey 11.11.2008, 11:07 | ||
По способу номер 3 в последнем моём посте - если хотите, то можете попробовать такой код (для подмены стандартного класса используется http://www.delphikingdom.ru/asp/viewitem.asp?catalogid=1367):
На первый взгляд вроде бы это работает, но, блин, серьёзно это не тестировалось. Я не рекомендую вам применять этот код как есть - это просто пример. Дело в том, что полностью ловить исключение в NewWndProc неправильно - т.к. существующий код (в том числе и VCL) рассчитывает на то, что исключение будет передаваться наружу. Поэтому вместо HandleException должно стоять запоминание экземпляра в глобальную переменную и её анализ и сброс в OnException. |
| Автор: Avers 11.11.2008, 11:08 | ||
| 1. Здесь идейка одна была и она заработала (по крайней мере в простом случае. Кажется, в другой теме я выкладывал этот код:
но это для малой программы, где всего один моудль, поэтому можно уверенно искать в медоах Selfю 2. Не спорю. Смотрю как работает эврика лог - загляденье. Но столь мощый обработчик не требуется (раз). Нужен свой, бесплатный (специфика работы, можно сказать) - это два. 3.... сложно и запутанно.... (имхо). Нужен просто модуль (реализация в виде пакета bpl) подключаемый к написанной программе. Т.е. так, чобы не переписывать код. |
| Автор: CodeMonkey 11.11.2008, 11:38 | ||||||
Вообще-то это число в точности равно переменной ExceptAddr ;)
Здесь как раз проблемы нет: вы всегда можете перебрать Screen.Forms. И перебрать методы у каждой формы. А настоящая проблема с приведённым способом в том, что он может давать ложную информацию. Например, пусть у вас в модуле есть (именно в таком порядке) OnButton1Click, затем в модуле идёт какой-то код (функция) и далее расположен Button2Click. Так вот, в Button2Click вы вызываете функцию, которая расположена между Button1Click и Button2Click. Предположим, что в ней возникнет исключение. Что сделает ваш код? Код возьмёт адрес исключения (он расположен где-то между Button1Click и Button2Click) и начнёт уменьшать его, сдвигаясь к Button1Click, на каждом шаге пытаясь получить имя метода. Поскольку код между Button1Click и Button2Click не входит в RTTI (это просто какая-то функция), то сделать вам этого не удасться. Это значит, что ближайшее место, где вы остановитесь - это Button1Click. В итоге, в реальности мы вызывали Button2Click -> SomeFunc, а обработчик исключений сообщит нам, что ошибка возникла в Button1Click. Ложная информация. Добавлено через 1 минуту и 22 секунды P.S. Это в точности та проблема, что я говорил чуть ранее: известны только адреса методов, но не их размер. |
| Автор: Avers 11.11.2008, 12:03 | ||
Упс)) Кста, как воспользоваться этой самой переменной?? Т.е. где она объявлена. Стыдно, но я не знаю. Я ею ни разу не пользовался. Добавлено через 2 минуты и 57 секунд Нашел)) |
| Автор: CodeMonkey 11.11.2008, 12:25 |
JclDebug - бесплатен. P.S. Эй, это же ваш вопрос: http://forum.vingrad.ru/forum/topic-231665.html Все ссылки сами и нашли. |
| Автор: Avers 12.11.2008, 09:52 |
| Можете считать меня упрямым, но я все равно хочу написать что-нить свое)) Буду развиваться, как программист=))) |
| Автор: CodeMonkey 12.11.2008, 10:49 |
| Ну так что, исчерпан вопрос или нет? Вариант ответа на исходный вопрос был. |
| Автор: Avers 12.11.2008, 10:59 |
| ответ на вопрос был. Признаюсь, про контейнеры - не очень ясно, но суть понял - Sender в MyException - малополезный параметр (имхо). Прост хотелось бы стабильности. Однорвеменно с тем появились новые вопросы. Например: Какие есть еще способы, кроме Self.MethodName, по адресу получить имя процедуры или фнуции.... Ведь, если посмотреть View -> Debug Windows -> Modules, то обнаружиться, что там есть адреса процедур и не принадлежащих объекту.... Application.MethodName - вообще ничего не дает (ни от одного адреса) Наверняка все проще, чем кажется... |
| Автор: CodeMonkey 12.11.2008, 11:16 |
Этот и другие (CallStack и т.п.) инструменты используют отладочную информацию. В готовую программу отладочная информация по-умолчанию не подключается. Попасть в exe она может несколькими способами, примеры каждого есть.... эээ... в JclDebug ;) Есть только единственный верный способ это сделать - воспользоваться отладочной информацией. Ваши попытки использовать для этого MethodName (читай: RTTI) некорректны, что я показал в http://forum.vingrad.ru/index.php?showtopic=235599&view=findpost&p=1695185. Ну нет в exe-файле информации о принадлежности адреса функции. Нету. Хоть головой об стенку бейтесь, но её там нет Единственный вариант - это её туда добавить. И вот здесь уже напридумывать можно много разных способов. Добавлено через 1 минуту и 50 секунд По самой идее - проще некуда ;) по реализации - готовых стандартных решений нет, писать нужно всё самому или использовать написанное другими. |
| Автор: Avers 12.11.2008, 11:29 | ||
Биться об стенку тоже вар)) Если хорошо удариться кодинг вообще не нужен станет Создатели EurekaLog заявляют, что используют только стандартную Debug Info.... Они же получают имя процедуры.... даже если, она "висит сама по себе".... Я в тупике. |
| Автор: CodeMonkey 12.11.2008, 12:09 | ||||||||
Давайте ещё раз. Приведу кусок текста из своей статьи про исключения:
Формат DCU файлов недокументирован и меняется в (почти) каждой версии Delphi. Очевидно, что пытаться брать отладочную информацию из DCU, как это делает отладчик в Delphi - не самая лучшая идея. Окей, тогда где её берут? Ну во-первых, есть две таких опции: "TD32 Debug Info" и "Remote Debug Info" (вкладка "Linker"). Включение опции заставляет компилятор добавить в exe секцию с данными, которые содержат всю отладочную информацию для exe в формате TD32 или удалённого отладчика Delphi. Это уже можно использовать - в частности JclDebug и EurekaLog при недоступности других источников берут информацию именно из отладочной информации для TD32 - см. например TJclDebugInfoTD32.InitializeSource (есть даже целый модуль JclTD32.pas). Но обычно это является запасным вариантом, а не основным. Почему? Потому что отладочная информация в этих форматах предназначена для внешнего (Turbo Debugger 32) и удалённого отладчика соответвенно. Это значит, что её формат оптимизирован для отладки, но не для ваших целей. В частности, с практической точки зрения это означает многократное (!) увеличение размера exe-файла. Ещё варианты? Окей, вот основной вариант:
Вот его и используют все готовые решения. Текстовый файл фиксированного формата. Что может быть проще? Читаем его, ищем нужный адрес и смотрим, какой функции, какому классу он принадлежит. Разумеется, читать (парсить) файл каждый раз - долго и неудобно (да и большой он). Именно поэтому большинство готовых решений при компиляции берут этот файл, выдёргивают из него нужную информацию, сохраняют её в удобном для себя формате и подключают её к exe-файлу. Получается аналог отладочной информации в формате TD32 в exe, только формат отладочной информации будет ваш личный. Кстати, готовые решения при этом часто ещё сжатие применяют. В среднем прирост размера exe файла составляет где-то 30%. JclDebug, например, получает информацию о имени функции по заданному адресу с помощью таких классов: - TJclDebugInfoMap - получает информацию из map-файла с тем же именем, что и exe-файл. - TJclDebugInfoBinary - получает информацию из встроенной в exe секции JCLDEBUG или jdbg-файла с тем же именем, что и exe-файл. - TJclDebugInfoTD32 - получает информацию из встроенной в exe секции TD32. - TJclDebugInfoSymbols - получает информацию из встроенной в exe секции виндовой отладочной информации - аналог TD32, только решение от Microsoft. Основным вариантом является TJclDebugInfoBinary, а остальные используются как запасные. EurekaLog, во-первых, использует JclDebug для получений той же информации: - TELDebugInfoMap - получает информацию из map-файла с тем же именем, что и exe-файл. - TELDebugInfoJedy - получает информацию из встроенной в exe секции JCLDEBUG или jdbg-файла с тем же именем, что и exe-файл. - TELDebugInfoTD32 - получает информацию из встроенной в exe секции TD32. Плюс добавляет свою информацию - реализация полностью аналогична TJclDebugInfoBinary/TELDebugInfoJedy, только формат не JDBG, а EurekaLog (отдельного класса нет, получение информации лежит леликом внутри GetSourceInfoByAddr). Этот вариант является для неё основным, а остальные используются как запасные. Т.е. как я и говорил - сама идея проста до безобразия (искать строчки в текстовом файле), но чтобы её реализовать самому.... попотеть придётся. |
| Автор: Avers 13.11.2008, 10:11 |
| Спасибо) большое) Вот. теперь все ясно и понятно... Как говориться, дело ясное, что дело темное Теперь ударюсь в способы работы с exe файлами)) надо подумать, как дополнить его нужной инфой, но при этом не попортить)) Добавлено через 2 минуты и 17 секунд Жаль у меня постов мало, а то бы обязательно плюсик поставил, поэому ограничусь человеческим спасибо. А за столь подробную информацию и нескольких +ов не жаль))) |
| Автор: CodeMonkey 13.11.2008, 11:25 |
Проще всего (и обычно так все и поступают) - добавить в exe файл пользовательский тип ресурса (RCData) с уникальным именем. Если я не ошибаюсь, то JCL добавляет секцию с именем JCLDEBUG, а EurekaLog - с ELDATA. |
| Автор: CodeMonkey 13.11.2008, 17:00 |
| P.S. Кстати, в текущей версии EL есть как минимум три серьёзные проблемы (Fabio пообещался исправить в следующей версии): 1. Используется RAW-метод трассировки. Это значит, что стек вызовов может содержать ложные элементы. По-хорошему нужна возможность использовать frame-based трассировку (на выбор, разумеется). 2. В стеке вызовов не показывается запись, если для неё нет отладочной информации. Видимо, подразумевалось, что это сгладит пункт 1. В реальности это сделало некоторые логи очень тяжело читаемыми. По-хорошему нужно показывать просто адрес без доп. информации. 3. Из map-файлов не забираются записи из модулей, для которых нет отладочной информации (в этом случае в map файле нет информации о строках, но по-крайней мере там есть список функций - вот его бы нужно брать, как это делает JCL). Например, для стандартных модулей (SysUtils и т.п.), если отключена опция "Use Debug DCUs". Это приводит проблемам в совокупности с пунктом 2. Лично для себя я вписываю в EL трассировку из JCL и модифицирую парсер. Ну пока это не поправят. |