__TRY, __CATCH, __FINALLY, __ENDTRY
Die Operatoren sind eine Erweiterung der Norm IEC 61131-3 und dienen einem gezielten Exception-Handling im IEC-Code.
Verfügbar ab TC3.1 Build 4024 für 32 Bit Laufzeitsysteme Verfügbar ab TC3.1 Build 4026 für 64 Bit Laufzeitsysteme |
Syntax:
__TRY
<try_statements>
__CATCH(exc)
<catch_statements>
__FINALLY
<finally_statements>
__ENDTRY
<further_statements>
Wenn eine Anweisung, die unter dem Operator __TRY steht, eine Exception produziert, hält das SPS-Programm nicht an. Sie führt stattdessen die Anweisungen unter __CATCH aus und startet damit das Exception-Handling. Danach führt sie die Anweisungen unter __FINALLY aus. Das Exception-Handling endet mit __ENDTRY. Dann führt das SPS-Programm die anschließenden Anweisungen aus (Anweisungen nach __ENDTRY).
Die Anweisungen des __TRY-Blocks, die unterhalb jener Anweisung stehen, die die Exception auslöst, werden nicht mehr ausgeführt. Das bedeutet, dass, sobald die Exception geworfen wird, die weitere Ausführung des __TRY-Blocks abgebrochen wird und die Anweisungen unter __CATCH ausgeführt werden.
Die Anweisungen unter __FINALLY werden immer ausgeführt, d. h. auch dann, wenn die Anweisungen unter __TRY keine Exception werfen.
Eine IEC-Variable für eine Exception hat den Datentyp __SYSTEM.ExceptionCode.
Hinweis | |
Maschinenstillstand durch abgefangene Gleitkomma-Ausnahmen auf x86-Zielsystemen Aufgrund einer technischen Einschränkung der x86-Plattform können abgefangene Ausnahmen, die durch eine Gleitkommaoperation verursacht werden, unbeabsichtigte, schwerwiegende Folgen haben: Das System kann in einen nicht wiederherstellbaren Zustand versetzt werden oder es kann zu einem Maschinenstillstand aufgrund eines Stacküberlaufs kommen. Dabei kann es ebenso vorkommen, dass der Maschinenstillstand nicht umgehend eintritt, sondern später bei weiteren Operationen, die ebenfalls auf dem Stack ausgeführt werden.
|
Beispiel
Der __TRY-Block des folgenden Beispiels enthält einen Zeigerzugriff und eine Division. Im ersten Zyklus wird innerhalb dieses Blocks eine „Access Violation“-Exception geworfen, da der Zeiger pSample zu diesem Zeitpunkt noch ein Null-Pointer ist. Die Verwendung eines Null-Pointers führt zu einer Exception. Im zweiten Zyklus wird innerhalb des __TRY-Blocks eine weitere Exception geworfen – dieses Mal eine Exception aufgrund einer Division durch 0.
Beide Exception-Ursachen werden innerhalb der __CATCH-Anweisungen behoben: die erste Exception durch eine Wertekorrektur des Zeigers pSample und die zweite Exception durch eine Wertekorrektur des Divisors nDivisor.
Dass bzw. wie das Exception-Handling funktioniert, kann zur Laufzeit wie folgt nachvollzogen werden:
- Da die fehlerhaften Zugriffe, die normalerweise zu einer Laufzeit-Exception und zu einem entsprechenden Stopp der Programmabarbeitung führen, innerhalb des Exception-Handlings abgefangen werden, bleibt die TC-Laufzeit im Run-Modus und die Programmabarbeitung läuft weiter.
- Da zwei Exceptions gefangen werden, wird der Zähler nCounter_CATCH zweimal inkrementiert und hat somit den Wert 2.
- Da in jedem Zyklus jeweils die nachfolgend beschriebenen Anweisungen ausgeführt werden, besitzen die dort inkrementierten Zähler alle denselben Wert. Der Wert entspricht der Anzahl der bisherigen Zyklen.
- die Anweisungen vor dem TRY-CATCH-Block = Inkrementieren des Zählers nCounter1
- die Anweisungen zu Beginn des __TRY-Blocks = Inkrementieren des Zählers nCounter_TRY1
- die Anweisungen unter __FINALLY = Inkrementieren des Zählers nCounter_FINALLY
- die Anweisungen nach dem TRY-CATCH-Block = Inkrementieren des Zählers nCounter2
- Da die Ausführung des __TRY-Blocks aufgrund der zwei geworfenen Exceptions zweimal abgebrochen wird, wird in zwei Zyklen das Ende der __TRY-Anweisungen nicht erreicht. Somit ist der Variablenwert von nCounter_TRY2 um 2 kleiner als der von nCounter_TRY1.
- Da die Ursachen für die beiden Exceptions innerhalb der __CATCH-Anweisungen behoben werden (pSample eine gültige Adresse sowie nDivisor einen Wert ungleich 0 zuweisen), treten die beiden Exceptions jeweils nur einmal auf.
- Die beiden Exceptions werden in dem Beispiel in ein globales Array zur Erstellung einer Exception-Historie gespeichert. Im Anschluss an die beiden Exceptions ist die entsprechende Indexvariable nExcIndex somit 2 und das Array aExceptionHistory besitzt die folgenden Werte:
- aExceptionHistory[0] = RTSEXCPT_ACCESS_VIOLATION
- aExceptionHistory[1] = RTSEXCPT_DIVIDEBYZERO
- aExceptionHistory[2] bis aExceptionHistory[10] = RTSEXCPT_NOEXCEPTION
- Im Anschluss an die beiden Exceptions ist die Variable exc zurück auf dem Default-Wert RTSEXCPT_NOEXCEPTION und die Variable lastExc zeigt mit RTSEXCPT_DIVIDEBYZERO den zuletzt aufgetretenen Exception-Code an.
Globale Variablenliste „GVL_Exc“:
{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
cMaxExc : UINT := 10;
END_VAR
VAR_GLOBAL
nExcIndex : UINT;
aExceptionHistory : ARRAY[0..cMaxExc] OF __SYSTEM.ExceptionCode;
END_VAR
Funktion „F_SaveExceptionCode“:
FUNCTION F_SaveExceptionCode
VAR_INPUT
excInput : __SYSTEM.ExceptionCode;
END_VAR
// Log the thrown exception into the global exception history array
IF GVL_Exc.nExcIndex <= GVL_Exc.cMaxExc THEN
GVL_Exc.aExceptionHistory[GVL_Exc.nExcIndex] := excInput;
GVL_Exc.nExcIndex := GVL_Exc.nExcIndex + 1;
END_IF
Programm „MAIN“:
PROGRAM MAIN
VAR
nCounter1 : INT;
nCounter2 : INT;
nCounter_TRY1 : INT;
nCounter_TRY2 : INT;
nCounter_CATCH : INT;
nCounter_FINALLY : INT;
exc : __SYSTEM.ExceptionCode;
lastExc : __SYSTEM.ExceptionCode;
pSample : POINTER TO BOOL;
bVar : BOOL;
nSample : INT := 100;
nDivisor : INT;
END_VAR
// Counter 1
nCounter1 := nCounter1 + 1;
// TRY-CATCH block
__TRY
nCounter_TRY1 := nCounter_TRY1 + 1;
pSample^ := TRUE; // 1. cycle: null pointer access leads to "access violation" exception
nSample := nSample/nDivisor; // 2. cycle: division by zero leads to "divide by zero" exception
nCounter_TRY2 := nCounter_TRY2 + 1;
__CATCH(exc)
nCounter_CATCH := nCounter_CATCH + 1;
// Exception logging
lastExc := exc;
F_SaveExceptionCode(excInput := exc);
// Correct the faulty variable values
IF (exc = __SYSTEM.ExceptionCode.RTSEXCPT_ACCESS_VIOLATION) AND (pSample = 0) THEN
pSample := ADR(bVar);
ELSIF ((exc = __SYSTEM.ExceptionCode.RTSEXCPT_DIVIDEBYZERO) OR (exc = __SYSTEM.ExceptionCode.RTSEXCPT_FPU_DIVIDEBYZERO)) AND (nDivisor = 0) THEN
nDivisor := 1;
END_IF
__FINALLY
nCounter_FINALLY := nCounter_FINALLY + 1;
__ENDTRY
// Counter 2
nCounter2 := nCounter2 + 1;