Geoff Thomson from Capesoft asked me about an example on how to override the RTL’s internal exception handler. I think this is a topic that is likely of interest to many Clarion developers and warrants an example, so here we go.
Overview
PROP:LastChanceHook is a write-only SYSTEM property, which allows you to specify your own function that will be invoked if an exception occurs. The hook function allows you to display information about the exception and choose one of the following actions:
- Continue execution of the thread where the exception occurred (unless the exception is fatal)
- Stop the thread (or the entire process if the exception occurred in the main process thread) without invoking the RTL’s internal exception handler
- Invoke the RTL’s internal exception handler
This allows you to catch an exception, and if it is non-fatal you can allow your end users to continue executing your program even when an exception has occurred in one of its threads.
How It Works
Your function, which you assign to the SYSTEM{PROP:LastChanceHook} property has to have a prototype of:
HookProc (*ICWExceptionInfo),LONG
The ICWExceptionInfo parameter is an interface declared in CWEXCPT.INT which you’ll find in your .Libsrc folder.
The result returned by the hooked function is evaluated as follows:
- If the result is equal to Zero, the RTL executes its own internal exception handler dialog to show information about the exception and subsequently perform the action chosen by the end user.
- If a Positive number is returned, the RTL stops the thread (or the entire process if the exception occurred in the main process thread) without invoking the RTL exception handler.
- If a Negative number is returned, the program will try to continue from the point of the exception. Note, if the exception is non-continuable, this result is ignored and treated as equal to Zero.
So in our example we start with this code:
PROGRAM
INCLUDE(‘CWEXCPT.INT’),ONCE
MAP
Test (LONG,LONG)
Hook (*ICWExceptionInfo),LONG
HEX (LONG),STRING,PRIVATE
MODULE(”)
MessageBox (UNSIGNED, CONST *CSTRING, CONST *CSTRING, UNSIGNED),SIGNED,PROC,PASCAL,NAME(‘MessageBoxA’)
END
END
MB_ICONHAND EQUATE(00000010h)
CODE
SYSTEM{PROP:LastChanceHook} = ADDRESS (Hook)
Test (10, 0) ! causes an exception
RETURN
In this example the Procedure named “Hook” is assigned as our exception handler. That’s the only Procedure we are concerned with, the others are just there to help make the example work by 1) causing an exception and 2) informing the user about the exception. Next we have:
! ------------------------------------------------------------------------------
Hook PROCEDURE (*ICWExceptionInfo info)
S CSTRING(1024)
Caption CSTRING(256)
CODE
IF info &= NULL
RETURN 0
END
Caption = ‘Exception ‘ & HEX (info.ExceptionCode()) & ‘ at ‘ & HEX (info.ExceptionAddress())
S = ‘Registers’ & |
‘<13,10>EAX=’ & HEX (info.Register (i386_Register:Reg32_EAX)) & |
‘ EBX=’ & HEX (info.Register (i386_Register:Reg32_EBX)) & |
‘ ECX=’ & HEX (info.Register (i386_Register:Reg32_ECX)) & |
‘ EDX=’ & HEX (info.Register (i386_Register:Reg32_EDX)) & |
‘<13,10>ESI=’ & HEX (info.Register (i386_Register:Reg32_ESI)) & |
‘ EDI=’ & HEX (info.Register (i386_Register:Reg32_EDI)) & |
‘ ESP=’ & HEX (info.Register (i386_Register:Reg32_ESP)) & |
‘ EBP=’ & HEX (info.Register (i386_Register:Reg32_EBP)) & |
‘<13,10,13,10>Current thread is being terminated’
MessageBox (0, S, Caption, MB_ICONHAND)
RETURN 1 ! a positive value signals the RTL to kill the thread
! ——————————————————————————
Test PROCEDURE (LONG a, LONG b)
CODE
a %= b
! ——————————————————————————
HEX PROCEDURE (LONG A)
i UNSIGNED,AUTO
S STRING(8),AUTO
DIGITS STRING(‘0123456789ABCDEF’),STATIC
CODE
i = SIZE(S)
LOOP WHILE i <> 0
S [i] = DIGITS [BAND (A, 0Fh) + 1]
A = BSHIFT (A, -4)
i -= 1
END
RETURN S
! ——————————————————————————
These first two lines of code assign our exception handler function and then call the Test procedure that raises an exception:
SYSTEM{PROP:LastChanceHook} = ADDRESS (Hook)
Test (10, 0) ! causes an exception
The exception is trapped and we show the result in a MessageBox which looks like this:
our Hook PROCEDURE (*ICWExceptionInfo info) uses the methods in the interface to show the exception code, its address, and the values of the registers at the time of the exception. And remember we said that:
“If a Positive number is returned, the RTL stops the thread (or the entire process if the exception occurred in the main process thread) without invoking the RTL exception handler.”
in our example our Hook PROCEDURE does a “RETURN 1” and since we are running on the main thread then immediately after the MessageBox is displayed and the user presses the OK button the program itself is terminated.
In a future article we’ll show you how to easily display the callstack so that your end-user can tell you exactly which procedure caused the exception. And we’ll be adding an option to the templates so that you can do the same with a couple of mouse clicks. You can download the source code for this example from this link
Robert,
Thanks for the heads up. We would really like to continue using the SetUnhandledExceptionFilter which seems to be overridden in C7.1. Is there any way you can provide us with a compile switch to use that turns off the RTL’s exception handler’s registratiion? According to the MSDN docs: “Issuing SetUnhandledExceptionFilter replaces the existing top-level exception filter for all existing and all future threads in the calling process”. But either this is not accurate, or the Clarion RTL uses an exception handler that supercedes this.
Looking forward to your comments.
Geoff
Great article, thanks! I’ll be adding this custom exception handling to my programs but I’ll probably wait until the templates support is in place
This article presents some very useful information. Consider adding it to the online help documentation. I can hardly wait for the followup.