Можно ли возобновить выполнение с того места, где возникло исключение? - PullRequest
8 голосов
/ 08 февраля 2012

Вот пример, который иллюстрирует мой вопрос:

procedure Test;
begin
  try
    ShowMessage('1');
    raise EWarning.Create(12345, 'Warning: something is happens!');
    ShowMessage('2');
  except
    on E: EWarning do
      if IWantToContinue(E.ErrorCode) then
        E.SkipThisWarning // shows '1' then '2'
      else
        E.StopExecution;  // shows '1'
  end;
end;

function IWantToContinue(const ErrorCode: Integer): Boolean;
begin
  //...
end;

Я пытался использовать что-то вроде этого:

asm
  jmp ExceptAddr
end;

но это не сработает ...

Есть идеи?

Спасибо.

Ответы [ 5 ]

8 голосов
/ 08 февраля 2012

Нет, это невозможно:

Существует два вида исключений: логические исключения, которые вызываются программистом с помощью команды raise, и внешние исключения, которые инициируются ЦП для различных условий: деление на ноль, переполнение стека, нарушение доступа. Для первого типа, логические исключения, вы ничего не можете сделать, потому что они являются частью «потока» приложения. Вы не можете связываться с потоком стороннего кода, вы даже не можете связываться с потоком своего собственного кода.

Внешние исключения

Обычно они возникают в результате выполнения одной инструкции ЦП, когда эта инструкция не выполняется. В Delphi они доступны как EExternal потомков. Список включает нарушения доступа, деление на ноль, переполнение стека, привилегированные инструкции и не так много других. Теоретически, для некоторых из этих исключений условие исключения может быть удалено, и одиночная инструкция ЦП повторяется, позволяя исходному коду продолжить работу, как будто ошибки не произошло. Например, НЕКОТОРЫЕ нарушения доступа могут быть «устранены» путем сопоставления страницы ОЗУ с адресом, где произошла ошибка.

В механизме SEH (Структурная обработка исключений), предоставляемом Windows, предусмотрены механизмы для устранения таких повторяющихся ошибок, и Delphi использует SEH под капотом. К сожалению, Delphi не предоставляет необходимые элементы, чтобы сделать это легко доступным, поэтому их использование будет очень трудным, если не невозможным. Тем не менее, для определенных типов ошибок EExternal умные дельфинианцы могут попытаться написать собственный код SEH и заставить его работать. Если это так, задайте новый вопрос, указав конкретный тип ошибки, которую вы получаете, плюс шаги, которые вы хотели бы предпринять, чтобы устранить условие ошибки: вы либо получите некоторый рабочий код, либо индивидуальное объяснение того, почему идея не сработает.

Логические исключения, инициированные с помощью raise

Большинство исключений попадают в эту категорию, потому что большая часть кода проверяет свои входные данные, прежде чем делать потенциально опасные вещи низкого уровня. Например, при попытке доступа к недопустимому индексу в TList этот индекс будет проверен, и перед попыткой доступа к запрошенному индексу возникнет исключительная ситуация недопустимого индекса. Без проверки доступ к недопустимому индексу либо вернет недействительные данные, либо вызовет нарушение прав доступа. Оба этих условия будет очень трудно отследить ошибки, поэтому исключение недопустимого индекса является очень хорошей вещью. Ради этого вопроса, даже если бы коду был разрешен доступ к недопустимому индексу, что вызвало нарушение прав доступа, было бы невозможно «исправить» код и продолжить, потому что невозможно угадать, каким должен быть правильный индекс.

Другими словами, исправление «логических» исключений не работает, не должно работать и безумно опасно. Если код, вызывающий ошибку, принадлежит вам, вы можете просто реструктурировать его, чтобы НЕ вызывать исключения для предупреждений. Если это не ваш код, то продолжение исключения попадает в категорию «безумно опасных» (не говоря уже о том, что это технически невозможно). Когда вы смотрите на уже написанный код, спросите себя: будет ли код работать правильно, если raise Exeption заменить на ShowMessage? Ответ в основном должен быть "NO, the code would fail anyway". В очень редком, очень неправильном случае стороннего кода, который вызывает исключение без уважительной причины, вы можете обратиться за конкретной помощью по исправлению кода во время выполнения, чтобы НИКОГДА не вызывать исключение.

Вот что может быть в каком-то стороннем коде:

function ThirdPartyCode(A, B: Integer): Integer;
begin
  if B = 0 then raise Exception.Create('Division by zero is not possible, you called ThirdPartyCode with B=0!');
  Result := A div B;
end;

Должно быть очевидно, что продолжение этого кода после исключения не позволит вещам "самовосстановиться".

Сторонний код также может выглядеть следующим образом:

procedure DoSomeStuff;
begin
  if SomeCondition then
    begin
      // do useful stuff
    end
  else
    raise Exception.Create('Ooops.');
end;

Где этот код "продолжить"? Совершенно очевидно, что это не часть "do usefull stuff", если код специально не разработан таким образом.

Это были, конечно, простые примеры, только царапающие поверхность.С технической точки зрения «продолжить» после исключения, как вы предлагаете, намного сложнее, чем перейти к адресу ошибки.Вызовы методов используют пространство стека для установки локальных переменных.Это пространство было освобождено в процессе «отката» после ошибки по пути к вашему обработчику исключений.Было выполнено finally блоков, возможно, при необходимости выделив ресурсы.Переход назад к исходному адресу был бы очень неправильным, потому что вызывающий код больше не имеет того, что он ожидает в стеке, его локальные переменные больше не являются тем, чем они должны быть.

Если поднимается ваш кодисключение

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

procedure Warning(const ErrorText:string);
begin
  if not UserWantsToContinue(ErrorText) then
    raise Exception.Create(ErrorText);
end;

// in your raising code, replace:
raise Exception.Create('Some Text');

// with:
Warning('Some Text');
3 голосов
/ 08 февраля 2012

AFAIK №Вы должны реструктурировать свой код к чему-то вроде

procedure Test;
begin
  ShowMessage('1');
  try
    raise EWarning.Create(12345, 'Warning: something is happens!');
  except
    on E: EWarning do
      if IWantToContinue(E.ErrorCode) then
        // shows '1' then '2'
      else
        raise; // shows '1'
  end;
  ShowMessage('2');
end;
1 голос
/ 09 февраля 2012

В C ++ возможно использование блока SEH __try/__except, выражение __except которого оценивается как EXCEPTION_CONTINUE_EXECUTION.

В Delphi невозможно напрямую использовать SEH, AFAIK.

0 голосов
/ 09 февраля 2012

.. вы можете вложить несколько try-кроме и определить, продолжать ли внутри каждого исключения:

try
  try
    some code
  except
    ret := message('want to continue?');
    if ret <> mrYes then
      exit;
  end;
  some other code to perform when no exception or user choose to continue
except
  etc..
end;
0 голосов
/ 08 февраля 2012

BASICally, ваш код не будет работать, потому что ExceptAddr является функцией , а не переменной .Итак, ваш фрагмент кода меняется следующим образом:

{$O-}
procedure TForm1.FormCreate(Sender: TObject);
begin
  try
    OutputDebugString('entering');
    raise Exception.Create('Error Message');
    OutputDebugString('leaving');
  except on E: Exception do
    begin
      OutputDebugString(PChar(Format('ExceptAddr = %p', [ExceptAddr])));
      asm
        CALL  ExceptAddr
        JMP   EAX
      end;
    end;
  end;
end;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...