今回の記事ではBeckhoff 4026で64Bit OSでも対応可能なTRY・CATCH・FINALLY・ENTRYを説明します。
さ、FAを楽しもう。
TRY・CATCH・FINALLY・ENTRY?
これらの演算子はIEC 61131-3規格を拡張したもので、IECコードの例外処理に使用されます。try、catch、finallyは、プログラミングの例外処理に使われる構文であり、JavaやJavaScriptなどの言語でよく見られます。
必須プラットフォーム
注意するのは4024バージョンで32 BitOSのみで使用可能です。
- TC3.1 Build 4024 for 32-bit runtime systems
- TC3.1 Build 4026 for 64-bit runtime systems
基本的な使い方
ここでは、これらの仕組みを見ていきましょう。
try block
try blockでは例外をスローする可能性のあるコードを記述します。例外がスローされなければ、catch blockはスキップされる。
catch block
try block内で例外が発生した場合、このブロック内のプログラムが実行されます。これによって例外を処理し、プログラムがクラッシュするのを防ぐことができます。
finally block
finally blockは、例外がスローされたかどうかに関係なく、tryとcatchの後に実行される。通常、クリーンアップ・タスクやリソースの解放に使われます。
キーポイント
先ほど書いてた基本的な使い方からまとめると、TRY・CATCH・FINALLYをうまく使用するにはこちらのポイントに気をつけましょう。
- try Blockは例外をスローする可能性のあるコードを含み、catch Blockは例外を処理します。
- finally Blockは例外が発生したかどうかに関係なく実行されるので、クリーンアップ処理に便利です。
- try、catch、finallyを使うことで、エラーをうまく処理できる堅牢なプログラムを書くことができます。
Statements
こちらはTRY・CATCH・FINALLYのStatementsになります。
__TRY <try_statements> __CATCH(exc) <catch_statements> __FINALLY <finally_statements> __ENDTRY <further_statements> |
最初に __Try の下に表示されるプログラムでエラーの例外が発生しても、PLC プログラムは停止しません。
その代わり、_CATCH以下の命令を実行し、例外処理を開始します。
そして、__FINALLY以下のプログラムが実行されます。注意するのは__FINALLYの下にある命令は常に実行されるので、すなわち、_TRYの下にある命令は例外をスローしなくても実行されます。
例外処理は __ENDTRY で終了し、その後、PLC プログラムは後続の命令 (__ENDTRY より後の命令) を実行し続けます。
__SYSTEM.ExceptionCode
エラー例外を表す IEC 変数のデータ型は __SYSTEM.ExceptionCodeは以下になります。。このような例外は、例えば __TRY, __CATCH, __FINALLY, __ENDTRY によって捕捉されます。ExceptionCodeデータ型を使用して、アプリケーション・コード自体で発生したエラーや例外を分類し、ログに記録することも可能です。
TYPE ExceptionCode : ( RTSEXCPT_UNKNOWN := 16#FFFFFFFF, RTSEXCPT_NOEXCEPTION := 16#00000000, RTSEXCPT_WATCHDOG := 16#00000010, RTSEXCPT_HARDWAREWATCHDOG := 16#00000011, RTSEXCPT_IO_CONFIG_ERROR := 16#00000012, RTSEXCPT_PROGRAMCHECKSUM := 16#00000013, RTSEXCPT_FIELDBUS_ERROR := 16#00000014, RTSEXCPT_IOUPDATE_ERROR := 16#00000015, RTSEXCPT_CYCLE_TIME_EXCEED := 16#00000016, RTSEXCPT_ONLCHANGE_PROGRAM_EXCEEDED := 16#00000017, RTSEXCPT_UNRESOLVED_EXTREFS := 16#00000018, RTSEXCPT_DOWNLOAD_REJECTED := 16#00000019, RTSEXCPT_BOOTPROJECT_REJECTED_DUE_RETAIN_ERROR := 16#0000001A, RTSEXCPT_LOADBOOTPROJECT_FAILED := 16#0000001B, RTSEXCPT_OUT_OF_MEMORY := 16#0000001C, RTSEXCPT_RETAIN_MEMORY_ERROR := 16#0000001D, RTSEXCPT_BOOTPROJECT_CRASH := 16#0000001E, RTSEXCPT_BOOTPROJECTTARGETMISMATCH := 16#00000021, RTSEXCPT_SCHEDULEERROR := 16#00000022, RTSEXCPT_FILE_CHECKSUM_ERR := 16#00000023, RTSEXCPT_RETAIN_IDENTITY_MISMATCH := 16#00000024, RTSEXCPT_IEC_TASK_CONFIG_ERROR := 16#00000025, RTSEXCPT_APP_TARGET_MISMATCH := 16#00000026, RTSEXCPT_ILLEGAL_INSTRUCTION := 16#00000050, RTSEXCPT_ACCESS_VIOLATION := 16#00000051, RTSEXCPT_PRIV_INSTRUCTION := 16#00000052, RTSEXCPT_IN_PAGE_ERROR := 16#00000053, RTSEXCPT_STACK_OVERFLOW := 16#00000054, RTSEXCPT_INVALID_DISPOSITION := 16#00000055, RTSEXCPT_INVALID_HANDLE := 16#00000056, RTSEXCPT_GUARD_PAGE := 16#00000057, RTSEXCPT_DOUBLE_FAULT := 16#00000058, RTSEXCPT_INVALID_OPCODE := 16#00000059, RTSEXCPT_MISALIGNMENT := 16#00000100, RTSEXCPT_ARRAYBOUNDS := 16#00000101, RTSEXCPT_DIVIDEBYZERO := 16#00000102, RTSEXCPT_OVERFLOW := 16#00000103, RTSEXCPT_NONCONTINUABLE := 16#00000104, RTSEXCPT_PROCESSORLOAD_WATCHDOG := 16#00000105, RTSEXCPT_FPU_ERROR := 16#00000150, RTSEXCPT_FPU_DENORMAL_OPERAND := 16#00000151, RTSEXCPT_FPU_DIVIDEBYZERO := 16#00000152, RTSEXCPT_FPU_INEXACT_RESULT := 16#00000153, RTSEXCPT_FPU_INVALID_OPERATION := 16#00000154, RTSEXCPT_FPU_OVERFLOW := 16#00000155, RTSEXCPT_FPU_STACK_CHECK := 16#00000156, RTSEXCPT_FPU_UNDERFLOW := 16#00000157, RTSEXCPT_VENDOR_EXCEPTION_BASE := 16#00002000, RTSEXCPT_USER_EXCEPTION_BASE := 16#00010000 ) UDINT ; END_TYPE |
メリット?
try、catch、finallyを使うと、プログラミングにおいていくつかの利点があります。
✓簡素化されたエラー処理
これにより、プログラムをクラッシュさせることなく例外を優雅に処理することができ、エラー管理が容易になります。
✓コードの明確性
Catch Blockの中で例外をどのように処理すべきかを明示することで、コードはより読みやすくなり、またエラーの場合に取られるアクションが明確に示されます。
✓資源管理
finally Blockは、ファイルやデータベース接続などのリソースが適切に解放されることを明確にプログラムでき、リソース漏れを防ぐのに役立ちます。
✓複数の例外処理
異なるタイプの例外を個別にキャッチし、処理することができます。これにより、特定のエラーに合わせた対応が可能になります。
✓より容易なデバッグ
例外が発生すると、例外の詳細をキャプチャしてログに記録できるため、問題の特定とデバッグが容易になります。
✓アプリケーションの安定性の向上
適切な例外処理を実装することで、アプリケーション全体の安定性が向上し、より良いユーザーエクスペリエンスにつながります。
Implementation
これからTRY・CATCH・FINALLY・ENTRYを利用しRUNTIMEの停止につながるエラーをCATCHし、Global Variable Listに記録しておきましょう。
DUT_History
こちらは100個のExceptionCodeを配列として定義し、例外が発生した場合のエラー内容を記録します。nExcIndexは次に記録される配列のIndexになります。
TYPE DUT_History : STRUCT nExcIndex : UINT:=0; aExceptionHistory : ARRAY[0..99] OF __SYSTEM.ExceptionCode; END_STRUCT END_TYPE |
F_ExceptionHisotryStack
次はCatchした例外を記録するFunction Blockを作成します。
VAR
こちらはFBのInterfaceになります。VAR_IN_OUTで先程定義したDUT_Historyをそのまま操作します。
FUNCTION F_ExceptionHisotryStack : BOOL VAR_INPUT InexcInput : __SYSTEM.ExceptionCode; END_VAR VAR_OUTPUT END_VAR VAR_IN_OUT ioData :DUT_History; END_VAR VAR _MaxIndex:DINT; END_VAR |
Program
こちらはプログラムの中身です。UPPER_BOUNDで配列のIndexを取り出し、これから記録するIndexが最大Indexより小さい場合、Exception 情報を格納し、Indexを+1にします。
_MaxIndex:= UPPER_BOUND(ioData.aExceptionHistory,1); IF ioData.nExcIndex <=_MaxIndex THEN ioData.aExceptionHistory[ioData.nExcIndex]:=InexcInput; ioData.nExcIndex:=ioData.nExcIndex+1; END_IF |
MAIN
最後はMAINプログラムで、無効のポインタ・アクセスと0を除算するエラーが入っています。
PROGRAM MAIN VAR History:DUT_History; END_VAR VAR nCounter_TRY1 : UINT; nCounter_TRY2 : UINT; nCounter_CATCH : UINT; nCounter_FINALLY : UINT; exc : __SYSTEM.ExceptionCode; lastExc : __SYSTEM.ExceptionCode; pSample : POINTER TO BOOL; bVar : BOOL; nSample : INT := 100; nDivisor : INT; END_VAR __TRY nCounter_TRY1 := nCounter_TRY1 + 1; pSample^ := TRUE; // null pointer access leads to “access violation” exception nSample := nSample/nDivisor; // 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_ExceptionHisotryStack(InexcInput:=exc,ioData:=History); // fix the error //correct the Pointer IF (exc = __SYSTEM.ExceptionCode.RTSEXCPT_ACCESS_VIOLATION) AND (pSample = 0) THEN pSample := ADR(bVar); //corret the Divide value 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 |
Error
こちらは無効のポインタ・アクセスと0を除算するプログラム部分になり、__TRY Blockの中で実行されています。
Catch
__TRY Blockで発生した例外が__CATCHに取られ、1つ目の例外はポインタpSampleの値補正、2つ目の例外は除数nDivisorの値を補正します。またこの2つの例外は、例外履歴を作成するためのグローバル配列に保存されています。
なので、結果的には配列aExceptionHistoryは以下の値を持ちます。
- aExceptionHistory[0] = RTSEXCPT_ACCESS_VIOLATION
- aExceptionHistory[1] = RTSEXCPT_DIVIDEBYZERO
- aExceptionHistory[2] to aExceptionHistory[99] = RTSEXCPT_NOEXCEPTION
Result
この通り、無効のポインタ・アクセスと0を除算するエラーが発生したとしてもTwinCAR Runtimeは止まりません。
Global Variable Listにはプログラム内に発生した例外が保存されてることがわかります。