Модераторы: Snowy, MetalFan, bems, Poseidon

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Sender и Exception, Так кто вызвал ошибку, а кто метод? 
V
    Опции темы
Avers
  Дата 10.11.2008, 11:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 113
Регистрация: 20.6.2008
Где: 26rus

Репутация: нет
Всего: нет



Не могу разобраься, какой объект вызвал ошибку, а какой метод (в котором эта ошибка возникла)
Простой пример: бросаем на форму кнопку и пишем код
Код

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(Sender.ClassName);
end;

Как бы кнопку не нажимали, в сообщении будет TButton
А если написать свой обработчик исключений))... начинается самое интересное
Код

procedure TForm1.MyException(Sender: TObject; E: Exception);
begin
  ShowMessage(Sender.ClassName);
 ...
end;

При фокусировке на кнопке жмем энтер и тоже получаем TButton. Но вот если нажать кнопку мышью, то получим TForm1 (!!!!)
Почему?
--------------------
Born to be wild
PM MAIL   Вверх
CodeMonkey
Дата 10.11.2008, 11:51 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 29
Всего: 89



Вообще-то метод, в котором возникло исключение, стандартными средствами не отслеживается никак.
Стандартный Application.HandleMessage всегда вызывается с Sender = форме, в методах которой расположен его вызов, а если он вызывается вне формы - то в Sender передаётся Application.
Если вас интересует диагностика места возникновения ошибки - см., например, Вопрос КС №57687


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
Avers
Дата 10.11.2008, 12:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 113
Регистрация: 20.6.2008
Где: 26rus

Репутация: нет
Всего: нет



В данный момент мне хотелось бы узнать, почему MyException передатся один раз TButton, другой раз TForm1
То, что нельзя легко и просто узнать метод, вызвавший ошибку, я смирюсь. Но хочется хотя бы знать объект и/или модуль, в котором ошибка возникла. 
--------------------
Born to be wild
PM MAIL   Вверх
CodeMonkey
Дата 10.11.2008, 12:56 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 29
Всего: 89



Цитата(CodeMonkey @  10.11.2008,  11:51 Найти цитируемый пост)
Стандартный Application.HandleMessage всегда вызывается с Sender = форме, в методах которой расположен его вызов, а если он вызывается вне формы - то в Sender передаётся Application.

Здесь я немного наврал. Не заметил:

Код
procedure TWinControl.MainWndProc(var Message: TMessage);
begin
  try
    try
      WindowProc(Message);
    finally
      FreeDeviceContexts;
      FreeMemoryContexts;
    end;
  except
    Application.HandleException(Self);
  end;
end;


Цитата(Avers @  10.11.2008,  12:01 Найти цитируемый пост)
почему MyException передатся один раз TButton, другой раз TForm1

Вероятно это связанно с особенностями обработки сообщений в Windows. Дело в том, что многие сообщения шлются не самому контролу, а его контейнеру. При низкоуровневом программировании это позволяет положить на окно кнопку, не создавая для неё отдельный класс, а управляя ей (ловить её сообщения) из контейнера. Соответственно VCL для эмуляции того поведения, что мы видим сейчас, должна ловить сообщения в контейнере и перенапрявлять их (прямым вызовом через Perform) дочерним контролам (CN_COMMAND с BN_CLICKED для случая OnClick кнопки). Обработчик исключений при этом остаётся установленным на контейнер. Такую работу выполняет, например, DoControlMsg. Соответственно, кто будет в Sender зависит от того, кому было направлено сообщение.

Цитата(Avers @  10.11.2008,  12:01 Найти цитируемый пост)
Но хочется хотя бы знать объект

Объект - это как? Объект - это вообще-то адрес в памяти. Вы хотите узнать число типа 0A34F3A7? smile
Обычно узнают класс и имя метода. Если  объект - это компонент, то при сильном желании можно вытащить его свойство Name. Только в этом случае некрасиво получится.


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
Avers
Дата 11.11.2008, 10:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 113
Регистрация: 20.6.2008
Где: 26rus

Репутация: нет
Всего: нет



я неверно выразился - мне нужно имя класса.
Т.е., если бы не было этой канители 
Цитата(Avers @  10.11.2008,  12:01 Найти цитируемый пост)
один раз TButton, другой раз TForm1

то, все было бы замечательно: по Sender можно было определить класс от которого пришло исключение, а следовательно и модуль и т.п. и во многих (пусть не всех, но во многих) случаях метод, в котором произошло исключение... (а мне больше и н надо).
Но не зная класса.... все остальное становится бессмыслено. 
Как же тогда определить модуль, в котором произошло исключение?
--------------------
Born to be wild
PM MAIL   Вверх
CodeMonkey
Дата 11.11.2008, 10:39 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 29
Всего: 89



Цитата(Avers @  11.11.2008,  10:04 Найти цитируемый пост)
по Sender можно было определить класс от которого пришло исключение

Ну вот поскольку 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 ведь в интерфейсной части, можно попробовать подменить его...


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
CodeMonkey
Дата 11.11.2008, 11:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 29
Всего: 89



По способу номер 3 в последнем моём посте - если хотите, то можете попробовать такой код (для подмены стандартного класса используется метод Geo):

Код
  TButton = class(StdCtrls.TButton)
  private
    FOldWndProc: TWndMethod;
    procedure NewWndProc(var Message: TMessage);
  public
    constructor Create(AOwner: TComponent); override;
  end;

{ TButton }

constructor TButton.Create(AOwner: TComponent);
begin
  inherited;
  FOldWndProc := WindowProc;
  WindowProc := NewWndProc;
end;

procedure TButton.NewWndProc(var Message: TMessage);
begin
  try
    FOldWndProc(Message);
  except
    Application.HandleException(Self);
  end;
end;


На первый взгляд вроде бы это работает, но, блин, серьёзно это не тестировалось. Я не рекомендую вам применять этот код как есть - это просто пример. Дело в том, что полностью ловить исключение в NewWndProc неправильно - т.к. существующий код (в том числе и VCL) рассчитывает на то, что исключение будет передаваться наружу. Поэтому вместо HandleException должно стоять запоминание экземпляра в глобальную переменную и её анализ и сброс в OnException.


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
Avers
Дата 11.11.2008, 11:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 113
Регистрация: 20.6.2008
Где: 26rus

Репутация: нет
Всего: нет



1. Здесь идейка одна была и она заработала (по крайней мере в простом случае.
Кажется, в другой теме я выкладывал этот код:
Код

procedure TForm1.MyException(Sender: TObject; E: Exception);
var
  i: integer;
  s: string;
begin
  if copy(e.Message,1,6)='Access' then // сделано для AccessViolation, но суть для всех одна
    begin
      i := strtoint('0x'+copy(e.Message,Pos('address',e.Message)+8,8)); // получаем адрес
      s := '';
      repeat
        s := Self.MethodName(Pointer(i)); // пыатемся получить имя метода 
        dec(i,2); // уменьшаем адрес
      until (s<>'');
      ShowMessage(s); // в этом сообщении мы и получим имя метода
    end;
end;

но это для малой программы, где всего один моудль, поэтому можно уверенно искать в медоах Selfю
2. Не спорю. Смотрю как работает эврика лог - загляденье. Но столь мощый обработчик не требуется (раз). Нужен свой, бесплатный (специфика работы, можно сказать) - это два.
3.... сложно и запутанно.... (имхо). Нужен просто модуль (реализация в виде пакета bpl) подключаемый к написанной программе. Т.е. так, чобы не переписывать код.
--------------------
Born to be wild
PM MAIL   Вверх
CodeMonkey
Дата 11.11.2008, 11:38 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 29
Всего: 89



Цитата(Avers @  11.11.2008,  11:08 Найти цитируемый пост)
Код
i := strtoint('0x'+copy(e.Message,Pos('address',e.Message)+8,8)); // получаем адрес

Вообще-то это число в точности равно переменной ExceptAddr ;)

Цитата(Avers @  11.11.2008,  11:08 Найти цитируемый пост)
это для малой программы, где всего один моудль, поэтому можно уверенно искать в медоах Self

Здесь как раз проблемы нет: вы всегда можете перебрать Screen.Forms. И перебрать методы у каждой формы.

А настоящая проблема с приведённым способом в том, что он может давать ложную информацию. Например, пусть у вас в модуле есть (именно в таком порядке) OnButton1Click, затем в модуле идёт какой-то код (функция) и далее расположен Button2Click. Так вот, в Button2Click вы вызываете функцию, которая расположена между Button1Click и Button2Click. Предположим, что в ней возникнет исключение. 
Что сделает ваш код? Код возьмёт адрес исключения (он расположен где-то между Button1Click и Button2Click) и начнёт уменьшать его, сдвигаясь к Button1Click, на каждом шаге пытаясь получить имя метода. Поскольку код между Button1Click и Button2Click не входит в RTTI (это просто какая-то функция), то сделать вам этого не удасться. Это значит, что ближайшее место, где вы остановитесь - это Button1Click.
В итоге, в реальности мы вызывали Button2Click -> SomeFunc, а обработчик исключений сообщит нам, что ошибка возникла в Button1Click. Ложная информация.

Добавлено через 1 минуту и 22 секунды
P.S. Это в точности та проблема, что я говорил чуть ранее: известны только адреса методов, но не их размер.


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
Avers
Дата 11.11.2008, 12:03 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 113
Регистрация: 20.6.2008
Где: 26rus

Репутация: нет
Всего: нет



Цитата(CodeMonkey @  11.11.2008,  11:38 Найти цитируемый пост)

Цитата(Avers @  11.11.2008,  11:08 )
i := strtoint('0x'+copy(e.Message,Pos('address',e.Message)+8,8)); // получаем адрес
Вообще-то это число в точности равно переменной ExceptAddr ;)

Упс)) Кста, как воспользоваться этой самой переменной?? Т.е. где она объявлена. 
Стыдно, но я не знаю. Я ею ни разу не пользовался.

Добавлено через 2 минуты и 57 секунд
Нашел))

Это сообщение отредактировал(а) Avers - 11.11.2008, 12:06
--------------------
Born to be wild
PM MAIL   Вверх
CodeMonkey
Дата 11.11.2008, 12:25 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 29
Всего: 89



Цитата(Avers @  11.11.2008,  11:08 Найти цитируемый пост)
Нужен свой, бесплатный

JclDebug - бесплатен.

P.S. Эй, это же ваш вопрос: http://forum.vingrad.ru/forum/topic-231665.html Все ссылки сами и нашли.

Это сообщение отредактировал(а) CodeMonkey - 11.11.2008, 12:27


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
Avers
Дата 12.11.2008, 09:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 113
Регистрация: 20.6.2008
Где: 26rus

Репутация: нет
Всего: нет



Можете считать меня упрямым, но я все равно хочу написать что-нить свое))
Буду развиваться, как программист=)))
--------------------
Born to be wild
PM MAIL   Вверх
CodeMonkey
Дата 12.11.2008, 10:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 29
Всего: 89



Ну так что, исчерпан вопрос или нет? 
Вариант ответа на исходный вопрос был.


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
Avers
Дата 12.11.2008, 10:59 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 113
Регистрация: 20.6.2008
Где: 26rus

Репутация: нет
Всего: нет



ответ на вопрос был. 
Признаюсь, про контейнеры - не очень ясно, но суть понял - Sender в MyException - малополезный параметр (имхо). Прост хотелось бы стабильности. 
Однорвеменно с тем появились новые вопросы. Например: 
Какие есть еще способы, кроме Self.MethodName, по адресу получить имя процедуры или фнуции....  
Ведь, если посмотреть View -> Debug Windows -> Modules, то обнаружиться, что там есть адреса процедур и не принадлежащих объекту.... Application.MethodName - вообще ничего не дает (ни от одного адреса)  smile 
Наверняка все проще, чем кажется... 
--------------------
Born to be wild
PM MAIL   Вверх
CodeMonkey
Дата 12.11.2008, 11:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 29
Всего: 89



Цитата(Avers @  12.11.2008,  10:59 Найти цитируемый пост)
если посмотреть View -> Debug Windows -> Modules

Этот и другие (CallStack и т.п.) инструменты используют отладочную информацию. В готовую программу отладочная информация по-умолчанию не подключается.
Попасть в exe она может несколькими способами, примеры каждого есть.... эээ... в JclDebug ;)

Цитата(Avers @  12.11.2008,  10:59 Найти цитируемый пост)
по адресу получить имя процедуры или фнуции

Есть только единственный верный способ это сделать - воспользоваться отладочной информацией. Ваши попытки использовать для этого MethodName (читай: RTTI) некорректны, что я показал в этом посте. Ну нет в exe-файле информации о принадлежности адреса функции. Нету. Хоть головой об стенку бейтесь, но её там нет smile
Единственный вариант - это её туда добавить. И вот здесь уже напридумывать можно много разных способов.

Добавлено через 1 минуту и 50 секунд
Цитата(Avers @  12.11.2008,  10:59 Найти цитируемый пост)
Наверняка все проще, чем кажется...

По самой идее - проще некуда ;) по реализации - готовых стандартных решений нет, писать нужно всё самому или использовать написанное другими.


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: Для новичков"
SnowyMetalFan
bemsPoseidon
Rrader

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Для новичков | Следующая тема »


 




[ Время генерации скрипта: 0.1021 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.