Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C++ Builder > Косяки (как не печально, но они бывают)


Автор: HappyLife 9.9.2008, 14:04
Ужасная вещь - косяк в программе. Он может однажды испортить все настроение, и вызывает все больше и больше недоверия со стороны руководства (ни капельки не смыслящих в программировании). Мне важно, научиться вести лог моей программы, и не прерывать её выполнения из-за ошибок. То есть я хочу перехватывать все исключения, брать их на заметку и продолжить выполнение программы, без всяких фатальных выскакивающих окошечек (типо Access Violetion) и полного ЗАВИСАНИЯ ПРОГРАММЫ. Полагаю, что нужно перегрузить главную функцию программы которая ловит ВСЕ сообщения, поставить там блок try catch
Но вот, что это за функция, которая ловит сообщения всего процесса, (и всех прилежащих DLL).
Спасибо за внимание.

Автор: ama_kid 9.9.2008, 14:19
а как try...catch может спасти от полного зависания в программе?

Цитата(HappyLife @  9.9.2008,  15:04 Найти цитируемый пост)
Полагаю, что нужно перегрузить главную функцию программы которая ловит ВСЕ сообщения, поставить там блок try catch
представь себе псевдо-код:
Код

int main(...)  // Главная функция в программе
{
 try
 {
    // Тут размещен весь код
    ...
    // Бац! Тут выскакивает неотловленный exception и ты не знаешь, где именно (если бы знал - отловил на месте)
    ...
  }
 catch()  
 {
   ...
   // Как ты отсюда планируешь попасть в ту точку, из которой вылетел?
   ...
 }
}
Не, я конечно читал статью, где небезызвестный Крис Касперски показывает как отловить BSOD, поэтому ничего невозможного нет, но стоит ли игра свеч?  smile Может имеет смысл просто как-нибудь отточить личную систему тестирования своих приложений?

Автор: HappyLife 9.9.2008, 14:30
У меня даже нет идей насчет системы личного тестирования моих приложений.
Например?

Я же планирую сделать что-то нижеследующего кода
Код

while(GetMessage(&msg,NULL,0,0))
{
    try
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    catch(Exception & e)
    {
        WriteToLog(...)
    }
}

И вот это мне нужно реализовать на бильдере. Учитывая что уже давно есть полу-готовый проект.





Автор: xvr 9.9.2008, 15:36
Цитата(HappyLife @ 9.9.2008,  14:04)
Ужасная вещь - косяк в программе. Он может однажды испортить все настроение, и вызывает все больше и больше недоверия со стороны руководства (ни капельки не смыслящих в программировании). Мне важно, научиться вести лог моей программы, и не прерывать её выполнения из-за ошибок. То есть я хочу перехватывать все исключения, брать их на заметку и продолжить выполнение программы, без всяких фатальных выскакивающих окошечек (типо Access Violetion) и полного ЗАВИСАНИЯ ПРОГРАММЫ. Полагаю, что нужно перегрузить главную функцию программы которая ловит ВСЕ сообщения, поставить там блок try catch
Но вот, что это за функция, которая ловит сообщения всего процесса, (и всех прилежащих DLL).
Спасибо за внимание.

Application->OnException

Автор: dumb 10.9.2008, 06:23
не буду напоминать, что просто продолжать выполнение после исключения, оставляя данные в неопределенном состоянии, - это зло.
но если уж встал на эту тропу, то советую воспользоваться какими-нибудь компонентами(http://forum.vingrad.ru/index.php?showtopic=149106&view=findpost&p=1118894) для облегчения поиска таких ошибок. в твоем случае надо просто сконфигурировать так, чтобы окно ошибки не показывалось, но в лог все писалось.

Добавлено через 1 минуту
Цитата(HappyLife @  9.9.2008,  15:04 Найти цитируемый пост)
Но вот, что это за функция, которая ловит сообщения всего процесса, (и всех прилежащих DLL).

SetUnhandledExceptionFilter

Автор: Vyacheslav 16.9.2008, 11:53
Цитата(dumb @  10.9.2008,  06:23 Найти цитируемый пост)
не буду напоминать, что просто продолжать выполнение после исключения, оставляя данные в неопределенном состоянии, - это зло.

Весьма спорное утверждение, если рассматривать  ее  не применительно  к конкретной ситуации, а в общем.  Кто сказал, что после генерации исключения данные окажутся в неопределенном состоянии? 
Или Вы предполагаете, что код пишется обязательно небезопасным?
А как же идеи о создании безопасного кода? Насколько я помню,   Дейвом Абрамсом было предложено  три уровня безопасного кода:
1) Базовый - когда при генерации исключения код  обеспечивает освобождение ресурсов и не допускает утечки памяти
2) Строгий - когда выполняются условия базового и состояние программы не изменяется
3) Без исключений - когда исключения  либо вообще не генерятся, либо  не выпускаются  наружу
В приниципе уже первые два позволяют построить программу так, что возможно нормальное продолжение работы программы даже после исключения. Последний - это весьма трудоемкий и маловероятный для реальной реализации вариант.
Например, пользователь  выполняет одну определенную бизнес-функцию. Во время выполнениния данной бизнес функции произошла ошибка, которая  была  зарегистрирована в лог. Если код программы обеспечил условие: состояние программы до начала и после  неудачного завершения бизнес-функции осталось прежним, кто мешает  повторить  бизнес функцию с новыми условиями или вообще временно до устранения  ошибки  не выполнять эту бизнес-функцию, а работать с другими?   
Слава богу, разоаботчики VCL подумали о такой возможности.В билдере разработчики уже предоставили возможность отлавливать большую часть exception и продолжить работу. Это как правильно заметил тут xvr , использование  Application->OnException. Остаются  только проблема с теми exception, которые не являются производными от Exception. Их вероятное появление придется отлавливать в коде и вместо низ генерить Exception. Для  этого достаточно  подозрительные места обложить try catch. В реалиях это не так уж трудно.

Например, у меня  централизованная обработка ошибок выполнялась так

Код

//---------------------------------------------------------------------------
void __fastcall TMainDataModule::ApplicationEventsException(
      TObject *a_Sender, Exception *a_Exc)
{
// Если это клиентское исключение, то просто выдаем сообщение...
    try
    {
        if (EipException* Exc = dynamic_cast<EipException*>(a_Exc))
        {
            Exc->Show();
            return;
        }
        // Если это серьезная ошибка
        std::auto_ptr<TErrorDialog> Dlg(new TErrorDialog(this, a_Exc,a_Sender));
        Dlg->ShowModal();
        // если ошибка произошла при входе в систему,
        // прекращаем работу
        if (m_Logining)
            Application->Terminate();
    }
    catch(...)
    {
    }
}



Как можно заметить, анормальный воход  производится только в одном случае: когда ошибка произошла на этапе регистрации пользователя в системе. Во все остальных вариантах решение принимает пользователь. Я лишь гаранитрую, что при  генерации  ошибки пользователь лишь не сможет выполнить определенную бизнес-функцию. При этом испортить он ничего не сможет.

Вот примерно такое окно. Если ошибка относится к БД, то появляется вторая вклака, по которой можно уточнить , где в БД произошала ошибка 

http://pic.ipicture.ru/uploads/080916/wAYWq7VZ5V.jpg

Его код
 
Код

const int FormaHeight = 420;
const AnsiString cHide = "Скрыть";

//---------------------------------------------------------------------------
__fastcall TErrorDialog::TErrorDialog(TComponent* a_Owner, Exception* a_Exc,
                                       TObject* a_Object )
    : TForm(Owner)
{
    AutoSize = true;
    Image->Picture->Icon->Handle = LoadIcon(NULL,MAKEINTRESOURCE(IDI_HAND));
    SecondRow->Caption =  Format("операцию по адресу %p",ARRAYOFCONST((ExceptAddr())));
    Message->Lines->Text = a_Exc->Message;
    Type->Caption =  a_Exc->ClassName();
    Module->Caption = GetExceptionModule(ExceptAddr());
    File->Caption = ExtractFileName(__ThrowFileName());
    Line->Caption = __ThrowLineNumber();
    if  (a_Object)
    {
     Object->Caption =  a_Object->ClassName();
     if ( IsPublishedProp(a_Object, cName) )
         ObjectName->Caption = GetStrProp(a_Object, cName);
    }
    if (EADSDatabaseError* Error = dynamic_cast<EADSDatabaseError*>(a_Exc))
    {
        EADSDatabaseErrorSheet->TabVisible = Error;
        ACEErrorCode->Caption         =  Error->ACEErrorCode;
        DatabaseName->Caption         =  Error->DatabaseName;
        DatabasePath->Caption         =  Error->DatabasePath;
        DataSetInstanceName->Caption  =  Error->DataSetInstanceName;
        DataSetInstanceOwner->Caption =  Error->DataSetInstanceOwner;
        SQLErrorCode->Caption         =  Error->SQLErrorCode;
        TableName->Caption            =  Error->TableName;
    }
}
//---------------------------------------------------------------------------

void __fastcall TErrorDialog::DetailsBtnClick(TObject *Sender)
{
    DetailPageControl->Visible = !DetailPageControl->Visible;
    AutoSize = ! DetailPageControl->Visible;
    if (DetailPageControl->Visible)
    {
        Height = FormaHeight;
        DetailsBtn->Caption = cHide;
    }
    else
        DetailsBtn->Caption = cInDetail;
}
//---------------------------------------------------------------------------





Ну и еще некоторый код
Код


AnsiString __fastcall InfoParma::GetExceptionModule(void* a_Addr)
{
     MEMORY_BASIC_INFORMATION mbi;
     if ( a_Addr && VirtualQuery(a_Addr,&mbi,sizeof(mbi)) )
     {
         char Path[ MAX_PATH ];
         GetModuleFileName((HMODULE)mbi.AllocationBase,Path,sizeof(Path));
         return ExtractFileName(AnsiString(Path));
     }
     return *NullStr;
}


Код

/////////////////////////////////////////////////////////////////////////////
// ## Назначение: Собственный класс исключения
// ## Описание :  В Message содержится код сообщения
class PACKAGE EipException: public Exception
{
private:
    bool m_NotCode;
    TVarRec* m_Args;
    int m_Size;
    TMsgDlgType  m_DlgType;
public:
#pragma option push -w-inl
    __fastcall EipException(AnsiString a_Msg):Exception(a_Msg){};
    __fastcall EipException(AnsiString a_Msg, TMsgDlgType a_DlgType)
                            :Exception(a_Msg),
                             m_NotCode(true),
                             m_DlgType( a_DlgType){};
#pragma option pop
    __fastcall EipException(AnsiString, const System::TVarRec*, const int);
    void __fastcall Show(void);
    __fastcall ~EipException(void);
};
//---------------------------------------------------------------------------




Код

//---------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////////////////
// ## Назначение: Конструктор
// ## Описание:   Используется, если необходимо сообщение параметризовано.
// ## Пример использования:
// ## throw  EipException("SYSTEM99",ARRAYOFCONST((UserName)));
// ## Аргументы:  a_Msg - к
// ## Возврат:
// ## Исключения:

__fastcall EipException::EipException(AnsiString a_Msg, const System::
                                     TVarRec* a_Args, const int a_Args_Size)
                       :Exception(a_Msg), m_Size(a_Args_Size + 1)
{
    m_Args = new TVarRec[m_Size];
    for(int i = 0; i < m_Size; ++i)
    {
        m_Args[i] =  a_Args[i];
    }
}
//---------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////////////////
// ## Назначение: Выводит диалоговое окно с сообщением
// ## Описание:   По коду ошибки формирует, соответствующее ему сообщение и
// ##             выводит диалоговое окно.
// ## Возврат:    Нет
// ## Исключения: Нет

void __fastcall EipException::Show(void)
{
    try
    {
        AnsiString Msg = Message;
        if (!m_NotCode && GetErrorMessage(Message, Msg, m_DlgType)&& m_Size)
            Msg = Format(Msg, m_Args, m_Size - 1);
        MessageDlg(Msg, m_DlgType,  TMsgDlgButtons() << mbOK);
    }
    catch(Exception& )
    {
        ShowException(ExceptObject(),ExceptAddr());
    }
}
//---------------------------------------------------------------------------
__fastcall EipException::~EipException(void)
{
    delete []m_Args;
}
//-------------------





Естесвенно, не вся информация об ошибках бывает доступна. Номер строки будет достпен только в том случае, если исключение сгенерировано Вами в коде с помощью throw, но и имеющейся информации бывает достаточно, чтобы  быстро локализовать ошибку



Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)