Ceci est déjà signalé comme RSP-25603: "Exception.RaiseOuterException peut provoquer un avertissement W1035 incorrect" .
Étant donné la fonction (de démonstration) suivante F , j'ai changé une instruction de levée d'exception pour maintenant enchaîner les exceptions:
--- before
+++ after
@@ -1,11 +1,11 @@
function F(X: NativeInt): NativeInt;
begin
try
Result := 1 div X;
except
on EDivByZero do
- {ECustom}Exception.Create('...');
+ Exception.RaiseOuterException({ECustom}Exception.Create('...'));
else
raise;
end;
end;
Maintenant, Ctrl-F9 donne l'avertissement W1035 :
[Avertissement dcc32]: W1035 La valeur de retour de la fonction 'F' est peut-être indéfinie
Cependant, tous les cas sont traités. Le compilateur ne parvient pas à reconnaître Exception.RaiseOuterException tant qu'opération de raise puissance.
Malheureusement FAcquireInnerException: Boolean est privé pour la classe Exception , même pas pour être défini sur True dans les classes personnalisées dérivées que je pourrais continuer à raise ECustomException.Create directement ( raise ECustomException.Create ).
Existe-t-il un moyen de faire comprendre au compilateur, tout en gardant les exceptions enchaînées? Sinon, je peux penser à {$Warn No_RetVal Off} . Sinon, comment pourrais-je contourner cet avertissement?
3 Réponses :
Une façon dont je peux penser pour éviter l'avertissement, sans le désactiver, est de faire ce qui suit à la place:
function F(X: NativeInt): NativeInt;
begin
try
Result := 1 div X;
except
on E: EDivByZero do
begin
Exception.RaiseOuterException({ECustom}Exception.Create('...'));
Result := 0; // <-- just to keep the compiler happy
end;
end;
end;
MISE À JOUR: Une autre façon, comme indiqué dans un commentaire, serait de simplement définir une valeur de retour qui n'est pas réellement atteinte au moment de l'exécution, par exemple:
function F(X: NativeInt): NativeInt;
begin
try
Result := 1 div X;
except
on E: Exception do
begin
if E is EDivByZero then
Exception.RaiseOuterException({ECustom}Exception.Create('...'));
raise;
end;
end;
end;
Bien sûr, il existe de nombreuses variantes. Par exemple, vous pouvez également supprimer l'avertissement en attribuant quelque chose à Result . Vous pouvez le faire après (!) L' RaiseOuterException . Dans le code d'origine de l'OP: Replace Exception.RaiseOuterException({ECustom}Exception.Create('...')); avec begin Exception.RaiseOuterException({ECustom}Exception.Create('...')); Exit(0) end;
Solution EChainedException
(comme demandé par Max)
Mise à jour J'ai publié un FR pour cela chez Embarcadero. Veuillez voter si vous aimez cette solution proposée. RSP-31679
En utilisant cette classe, l'exception interne est toujours enregistrée "comme si" vous aviez appelé Exception.RaiseOuterException . Cela vous permet d'utiliser la simple instruction raise , cela évite que le message d'avertissement soit émis par le compilateur.
Utilisation
EChainedException simplement vos exceptions personnalisées de EChainedException au lieu d' Exception , et utilisez raise plutôt Exception.RaiseOuterException .
Code source
Le code correspondant est ci-dessous. Mon EChainedException complet est un peu plus compliqué que cela pour supporter la détection des exceptions fatales et le stacktracing, etc. S'il ne compile pas, faites-moi savoir ce qui manque et j'ajouterai la partie manquante.
unit uChainedException;
interface
uses Sysutils;
{$M+} // ensures RTTI info is present for EChainedException
type
EChainedException = class(Exception)
protected
procedure RaisingException(P: system.sysutils.PExceptionRecord); override;
end;
implementation
uses rtti;
var // rtti pointers for handling the inner exception
vInnerExceptionOffset: NativeInt = -1;
vAcquireInnerExceptionOffset: NativeInt = -1;
vRunningInIDEInitialized: Boolean;
vRunningInIDE: Boolean;
function RunningInIDE:boolean;
begin
if not vRunningInIDEInitialized then
begin
vRunningInIDE:=AnsiSameText(ExtractFileName(ParamStr(0)),'BDS.EXE');
vRunningInIDEInitialized:=True;
end;
Result:=vRunningInIDE;
end;
procedure EChainedException.RaisingException(P: System.sysutils.PExceptionRecord);
var
PBoolean: ^Boolean;
PObject : ^TObject;
begin
if (ExceptObject<>self) and (vAcquireInnerExceptionOffset >=0) then
begin
PBoolean := Pointer(NativeInt(Self)+vAcquireInnerExceptionOffset);
PBoolean^ := PBoolean^ or not RunningInIDE;
end;
inherited;
// in some rare cases (like reraise exception from another thread)
// it may happen that the innerexception points to self
// this is corrected here.
if InnerException=self then
begin
PObject := Pointer(NativeInt(Self)+vInnerExceptionOffset);
PObject^ := nil;
end;
end;
procedure UnprepAutoInnerException;
begin
vInnerExceptionOffset:=-1;
vAcquireInnerExceptionOffset:=-1;
end;
procedure PrepAutoInnerException;
var
lRTTIContext: TRttiContext;
lInnerException:TRttiField;
lAcquireInnerException:TRttiField;
lClass: TRttiInstanceType;
begin
try
lRTTIContext.Create; //Notice vRTTIContext is a record, .Create initializes properties
try
lClass:=lRTTIContext.GetType(Exception) as TRttiInstanceType;
lInnerException:=lClass.GetField('FInnerException');
vInnerExceptionOffset := lInnerException.Offset;
lAcquireInnerException:=lClass.GetField('FAcquireInnerException');
vAcquireInnerExceptionOffset := lAcquireInnerException.Offset;
except
UnprepAutoInnerException;
raise;
end;
finally
lRTTIContext.Free;
end;
end;
initialization
PrepAutoInnerException;
finalization
UnprepAutoInnerException;
end.
En regardant ce code, je trouve qu'il pourrait utiliser une certaine modernisation, par exemple en utilisant des variables de classe au lieu de globals, et en utilisant des variables locales en ligne. L'unité entière est de retour de Delphi 6 jours et contient de nombreux $ ifdefs, et laissée de côté car elle surpasserait la réponse.
Je me demande toujours pourquoi le chaînage d'exceptions n'est pas la valeur par défaut dans delphi / rad studio comme c'est le cas dans d'autres langues. Très probablement parce que cela briserait le code existant d'une manière ou d'une autre.
Merci beaucoup pour votre code! +1 Ce n'était pas difficile de comprendre les parties manquantes, alors je suis allé de l'avant et je les ai ajoutées moi-même à votre réponse, afin qu'elle soit compilée. Votre réponse m'a orienté vers une autre solution. Voyez ma réponse . J'espère qu'il y a quelque chose pour vous aussi !!
Je réponds (également) à ma propre question car je vais adopter une autre approche. Il prévoit les exigences suivantes:
raise , comme elles l'étaient initialement,Dans ma solution:
Exception , FAcquireInnerException particulier.ExceptionFields , selon Exception ).Ici, je propose une implémentation condensée à copier-coller:
Le constructeur d' EException présente l'utilisation d' ExceptionFields :
EDemoException: Level 1 EException: Level 2 EZeroDivide: Level 3
- à utiliser dans toute Exception dérivée d'une exception, et cela déclenchera le mécanisme RTL pour définir InnerException pendant qu'il déclenche l'exception. En outre, EException peut servir de racine commune pour les classes d'exceptions personnalisées, si vous le souhaitez. Certains constructeurs sont réintroduits pour être étendus avec const AcquireInnerException: Boolean = True , pour remettre le contrôle à l'appelant tout en fournissant une valeur par défaut pour le chaînage souhaité.
Exécutez ExceptionFields.VerifyFieldAlignments , si vous souhaitez vérifier les alignements de
ExceptionFields etException . S'il ne peut pas vérifier cela, il lèvera une exception. Il est exécuté dans le constructeur de classe d' EException . Déplacez-le comme vous le souhaitez, si vous n'utilisez pas EException , mais que vous souhaitez conserver la vérification.
Mise en œuvre (condensée):
program ExceptionsDemo;
{$AppType Console}
{$R *.res}
uses
System.SysUtils,
Exceptions in 'Exceptions.pas';
type
EDemoException = class (EException)
end;
begin
try
try
try
raise EZeroDivide.Create('Level 3');
except
raise EException.Create('Level 2', False);
end;
except
raise EDemoException.Create('Level 1');
end;
except
on E: Exception do
begin
WriteLn(E.ClassName, ': ', E.Message);
while Assigned(E.InnerException) do
begin
E := E.InnerException;
WriteLn(E.ClassName, ': ', E.Message);
end;
end;
end;
ReadLn;
end.
Et une démo:
unit Exceptions;
interface
uses
System.SysUtils;
type
EException = class (Exception)
public
class constructor Create;
constructor Create(const Msg: String; const AcquireInnerException: Boolean = True);
constructor CreateFmt(const Msg: String; const Args: array of const; const AcquireInnerException: Boolean = True); overload;
constructor CreateRes(const Msg: PResStringRec; const AcquireInnerException: Boolean = True);
constructor CreateResFmt(const Msg: PResStringRec; const Args: array of const; const AcquireInnerException: Boolean = True); overload;
end;
type
ExceptionFields = class (TObject)
{$Hints Off} // H2219
strict private
FMessage: String;
FHelpContext: Integer;
FInnerException: Exception;
FStackInfo: Pointer;
{$Hints On}
public
FAcquireInnerException: Boolean;
private
class procedure VerifyFieldAlignments;
end;
implementation
uses
System.Generics.Collections,
System.RTTI,
System.TypInfo;
{ ExceptionFields }
class procedure ExceptionFields.VerifyFieldAlignments;
procedure RaiseTypeNotFound(const ClassName: String);
begin
raise Exception.CreateFmt(
'Typ nicht gefunden: %s',
[ClassName]
);
end;
procedure RaiseFieldNotFound(const ClassName, FieldName: String);
begin
raise Exception.CreateFmt(
'Feld nicht gefunden: %s.%s',
[ClassName, FieldName]
);
end;
procedure RaiseFieldNotAligned(const LeftClassName: String; const LeftField: TPair<String, Integer>; const RightClassName: String; const RightField: TRTTIField);
begin
raise Exception.CreateFmt(
'Feld nicht ausgerichtet: %s.%s+%d (tatsächlich) vs. %s.%s+%d (erwartet)',
[
LeftClassName,
LeftField.Key,
LeftField.Value,
RightClassName,
RightField.Name,
RightField.Offset
]
);
end;
type
TMemberVisibilities = set of TMemberVisibility;
function GetDeclaredFields(const RTTIContext: TRTTIContext; const &Class: TClass; const IncludedVisibilities: TMemberVisibilities = [mvPublic, mvPublished]): TArray<TPair<String, Integer>>;
var
RTTIType: TRTTIType;
RTTIFields: TArray<TRTTIField>;
Index: NativeInt;
RTTIField: TRTTIField;
begin
RTTIType := RTTIContext.GetType(&Class);
if not Assigned(RTTIType) then
RaiseTypeNotFound(&Class.ClassName);
RTTIFields := RTTIType.GetDeclaredFields;
SetLength(Result, Length(RTTIFields));
Index := 0;
for RTTIField in RTTIFields do
if RTTIField.Visibility in IncludedVisibilities then
begin
Result[Index] := TPair<String, Integer>.Create(
RTTIField.Name,
RTTIField.Offset
);
Inc(Index);
end;
SetLength(Result, Index);
end;
const
Left: TClass = ExceptionFields;
Right: TClass = Exception;
var
RTTIContext: TRTTIContext;
DeclaredFields: TArray<TPair<String, Integer>>;
RTTIType: TRTTIType;
DeclaredField: TPair<String, Integer>;
RTTIField: TRTTIField;
begin
RTTIContext := TRTTIContext.Create;
try
DeclaredFields := GetDeclaredFields(RTTIContext, Left);
RTTIType := RTTIContext.GetType(Right);
if not Assigned(RTTIType) then
RaiseTypeNotFound(Right.ClassName);
for DeclaredField in DeclaredFields do
begin
RTTIField := RTTIType.GetField(DeclaredField.Key);
if not Assigned(RTTIField) then
RaiseFieldNotFound(Right.ClassName, DeclaredField.Key);
if DeclaredField.Value <> RTTIField.Offset then
RaiseFieldNotAligned(
Left.ClassName, DeclaredField,
RTTIType.Name, RTTIField
);
end;
finally
RTTIContext.Free;
end;
end;
{ EException }
class constructor EException.Create;
begin
inherited;
ExceptionFields.VerifyFieldAlignments;
end;
constructor EException.Create(const Msg: String;
const AcquireInnerException: Boolean);
begin
inherited Create(Msg);
ExceptionFields(Self).FAcquireInnerException := AcquireInnerException;
end;
constructor EException.CreateFmt(const Msg: String;
const Args: array of const;
const AcquireInnerException: Boolean);
begin
inherited CreateFmt(Msg, Args);
ExceptionFields(Self).FAcquireInnerException := AcquireInnerException;
end;
constructor EException.CreateRes(const Msg: PResStringRec;
const AcquireInnerException: Boolean);
begin
inherited CreateRes(Msg);
ExceptionFields(Self).FAcquireInnerException := AcquireInnerException;
end;
constructor EException.CreateResFmt(const Msg: PResStringRec;
const Args: array of const;
const AcquireInnerException: Boolean);
begin
inherited CreateResFmt(Msg, Args);
ExceptionFields(Self).FAcquireInnerException := AcquireInnerException;
end;
end.
Sortie - la dernière ligne est uniquement là lors de l' raise EException.Create('Level 2', True) :
ExceptionFields(Self).FAcquireInnerException := True;
Merci à tous les répondants!
C'est une solution intéressante pour contourner la solution RTTI. Je suis principalement allé pour le poids léger utilisant des pointeurs (et non TrttiField.SetValue) en raison des performances. De plus, comme vous, je saurai immédiatement si Emb décide de changer. Renommer FInnerException puisque RTTI ne pourra pas les trouver. Pensez-y: avez-vous envisagé d'utiliser une classe d'aide pour faire le truc du piratage?
Quoi qu'il en soit, assurez-vous de vérifier quality.embarcadero.com/browse/RSP-31679
J'ai souvent une sous-
procedure Inv; begin raise EFrogProperties.Create('Invalid frog properties.'); end;locale comme laprocedure Inv; begin raise EFrogProperties.Create('Invalid frog properties.'); end;et aimerait aussi un mot-clénoreturnpour que le compilateur sache qu'il ne reviendra jamais à l'appelant.J'ai toujours aimé la conception de
RaiseOuterException(). Pourquoi ils n'ont pas simplement introduit un nouveau constructeur qui capture uneExceptionexistante ou acquiert l'Exceptionactuelle, ou au moins une méthodeCreateOuterException()qui retourne une nouvelleExceptionqui peut êtreraiseséparément, est derrière moi.@RemyLebeau: Juste être curieux: que diriez-vous d'un mot-clé ou d'un attribut
noreturn? (Peut-être pas pour résoudre ce problème particulier, mais en général.)J'ai créé une classe EChainedException qui, lorsqu'elle est levée, capture toujours l'exception interne (comme Exception.RaiseOuterExcepotion). Comme vous pouvez utiliser relancer directement, l'avertissement disparaîtra également, mais les excès seront toujours enchaînés
@ H.Hasenack En regardant l'implémentation,
private FAcquireInnerException: Boolean = Falserend l'ensemble du mécanisme d'Exceptioninaccessible; c'est-à-dire que laprotected procedure SetInnerExceptiondevient un no-op pour les classes détruites, par exemple. Réimplémentez-vous la mécanique? Ce sont pas mal de choses à suivre pour rester à égalité avecException. OuFAcquireInnerExceptionla visibilité deFAcquireInnerException? Souhaitez-vous partager le code de votreEChainedException?