catch - manejo de excepciones en delphi
¿Cómo debo volver a plantear una excepción de Delphi después de iniciar sesión? (7)
¿Conoces una forma de atrapar, registrar y volver a subir la excepción en el código Delphi? Un simple ejemplo:
procedure TForm3.Button1Click(Sender: TObject);
begin
try
raise Exception.Create(''Bum'');
except
on E: Exception do
begin
MyHandleException(E);
end;
end;
end;
procedure TForm3.MyHandleException(AException: Exception);
begin
ShowMessage(AException.Message);
LogThis(AException.Message);
// raise AException; - this will access violate
end;
Así que tengo que volver a subirlo en el bloque excepto, pero me preguntaba si hay una mejor manera de escribir mi propio método para manejar y (en condiciones específicas) volver a plantear excepciones.
Debería poder usar el comando Raise
solo para volver a subir la excepción:
begin
MyHandleException(E);
Raise;
end;
Lo siguiente funcionará, pero por supuesto no es ideal por dos razones:
- La excepción se plantea desde un lugar diferente en la pila de llamadas.
- No obtiene una copia exacta de la excepción, especialmente aquellas clases que agregan atributos. Es decir, tendrá que copiar explícitamente los atributos que necesita.
- La copia de atributos personalizados puede complicarse debido a la verificación de tipos requerida.
.
procedure TForm3.MyHandleException(AException: Exception);
begin
ShowMessage(AException.Message);
LogThis(AException.Message);
raise ExceptClass(AException.ClassType).Create(AException.Message);
end;
Los beneficios son que conserva la clase de excepción original y el mensaje (y cualquier otro atributo que desee copiar).
Lo ideal es llamar a System._RaiseAgain
, pero, por desgracia, es una rutina de "compilación mágica" y solo se puede llamar mediante raise;
.
Puede adquirir el objeto de excepción antes de llamar a su controlador y mantener el controlador en sí mismo un liner. Sin embargo, todavía tiene una gran cantidad de carga "Try / Except / Do / End".
Procedure MyExceptionHandler(AException: Exception);
Begin
Log(AException); // assuming it accepts an exception
ShowMessage(AException.Message);
raise AException; // the ref count will be leveled if you always raise it
End;
Procedure TForm3.Button1Click(Sender: TObject);
Begin
Try
Foo;
Except On E:Exception Do
MyExceptionHandler(Exception(AcquireExceptionObject));
End;
End;
Sin embargo, si lo único que desea hacer es deshacerse del código de manejo de errores repetitivo en los controladores de eventos, podría intentar esto:
Procedure TForm3.ShowException(AProc : TProc);
Begin
Try
AProc;
Except On E:Exception Do Begin
Log(E);
ShowMessage(E.Message);
End; End;
End;
Reduciendo su código de controlador de eventos a esto:
Procedure TForm3.Button1Click(Sender: TObject);
Begin
ShowException(Procedure Begin // anon method
Foo; // if this call raises an exception, it will be handled by ShowException''s handler
End);
End;
También puede hacer que funcione para funciones, usando funciones parametrizadas:
Function TForm3.ShowException<T>(AFunc : TFunc<T>) : T;
Begin
Try
Result := AFunc;
Except On E:Exception Do Begin
Log(E);
ShowMessage(E.Message);
End; End;
End;
Y hacer que ShowException devuelva un valor (actuando como un passthru):
Procedure TForm3.Button1Click(Sender: TObject);
Var
V : Integer;
Begin
V := ShowException<Integer>(Function : Integer Begin // anon method
Result := Foo; // if this call raises an exception, it will be handled by ShowException''s handler
End);
End;
O incluso haciendo que el procedimiento anon toque directamente la (s) variable (s) del ámbito externo:
Procedure TForm3.Button1Click(Sender: TObject);
Var
V : Integer;
Begin
ShowException(Procedure Begin // anon method
V := Foo; // if this call raises an exception, it will be handled by ShowException''s handler
End);
End;
Existen algunas limitaciones en la interacción de variables desde el interior del cuerpo de la función anónima y las definidas en el ámbito externo, pero para casos simples como estos, estará más que bien.
Puede intentar usar (system.pas):
function AcquireExceptionObject: Pointer;
AcquireExceptionObject devuelve un puntero al objeto de excepción actual y evita que el objeto de excepción sea desasignado cuando se cierra el controlador de excepción actual.
Nota: AcquireExceptionObject incrementa el recuento de referencias del objeto de excepción. Asegúrese de que el recuento de referencias disminuya cuando ya no se necesite el objeto de excepción. Esto sucede automáticamente si usa el objeto de excepción para volver a generar la excepción. En todos los demás casos, cada llamada a AcquireExceptionObject debe tener una llamada correspondiente a ReleaseExceptionObject. Las secuencias de AcquireExceptionObject / ReleaseExceptionObject se pueden anidar.
Si desea volver a plantear la excepción solo bajo ciertas condiciones, escriba
procedure TForm3.Button1Click(Sender: TObject);
begin
try
raise Exception.Create(''Bum'');
except
on E: Exception do
begin
if MyHandleException(E) then
raise;
end;
end;
end;
function TForm3.MyHandleException(AException: Exception): boolean;
begin
ShowMessage(AException.Message);
result := true/false;
end;
Siguiendo con la publicación de Craig Young, utilicé algo similar al siguiente código con éxito. Puede conservar la ubicación de excepción original utilizando el identificador "at" con la función ExceptAddr. El tipo de clase de excepción original y la información también se conservan.
procedure MyHandleException(AMethod: string);
var
e: Exception;
begin
e := Exception(AcquireExceptionObject);
e.Message := e.Message + '' raised in '' + AMethod;
raise e at ExceptAddr;
end;
try
...
except
MyHandleException(''MyMethod'');
end;
Viejo tema pero, ¿qué pasa con esta solución?
procedure MyHandleException(AException: Exception);
begin
ShowMessage(AException.Message);
AcquireExceptionObject;
raise AException;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
try
raise Exception.Create(''Bum'');
except
on E: Exception do
MyHandleException(E);
end;
end;
Se basa en el primer código publicado por Eduardo.