[Win32] Exception handling using Try/Catch/Finally

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
Cherry
Posts: 358
Joined: Oct 23, 2007 12:06
Location: Austria
Contact:

[Win32] Exception handling using Try/Catch/Finally

Post by Cherry »

Hello!

I have created some macros for implementing Try/Catch/Finally blocks. Unfortunately, you have to give each Try block a name, because I found no way to create a stack and a counter using the preprocessor...

I somebody doesn't know: With Try/Catch/Finally blocks you can "catch" errors (either errors you have "thrown" by yourself or things like access violations (access to an invalid memory address) and similar). If such an error occurs inside a Try block, the program flow will jump to the corresponding Catch statement of this, or, if there is none, of the next higher Try block. If the Try block is left, the code below the Finally statement will be executed, regardless whether an exception occured or not - you can do cleanup tasks like closing files or deallocating memory here.

Try(name) : Starts a Try block

Catch(name, exception codes) : Catches one or more exceptions. "exception codes" can be any values which are possible with the "Case" statement in FreeBasic, but you have to use "__" (2 underscores) instead of a comma as separator.
Examples:

Code: Select all

Catch(name, EXCEPTION_ACCESS_VIOLATION)
Catch(name, EXCEPTION_ACCESS_VIOLATION __ EXCEPTION_INT_DIVIDE_BY_ZERO)
Catch(name, Is <> EXCEPTION_INVALID_INSTRUCTION)
Catch(name, &hC0000000 To &hC000FFFF)
Catch(name, Else) ; this would catch all other exceptions
A list of all exception codes of Windows you can find here: http://xurl.sh/exceptioncodes

Finally(name) : Code below this statement will always be executed, even if an exception occured.

EndTry(name) : Closes a Try block

Leave(name) : Executes the "Finally" code. For example, Leave can be called before jumping out of the Try block, e.g. by using "Return" or "Exit Sub".

ExitTry(name) : Executes the "Finally" code and exits the Try block

Inside the "Catch" code, there can also be used the following variables:

ECode : Returns the exception code

EAddress : Returns the address where the exception occured

ENumParams : Returns the number of parameters (of the exception)

EParamsPtr : Returns the pointer to the array of parameters

EParams(i) : Returns the parameter #i (starting with 0). If you specify an invalid id, 0 will be returned. At EXCEPTION_ACCESS_VIOLATION, EParams(1) will hold the address which the program tried to access, and EParams(0) will tell you if it was reading (0) or writing (1) to this address.

ERecord : Pointer to the EXCEPTION_RECORD of the exception.

EContext : Pointer to the CONTEXT of the exception.

In order to "throw" exceptions by yourself, you can use these commands:

Raise(exception code) : Throws an exception without parameters

RaiseParam(exception code, number of parameters, parameters) : Throws an exception with parameters. The parameters (Integers) need to be separated using "__".

Please keep in mind that Windows always clears bit 28 of the exception code, it's used internally. The exception codes should also follow this rules:

Code: Select all

Bits 31-30        Bit 29           Bit 28           Bits 27-0
0=success         0=Microsoft      Reserved         For exception
1=information     1=Application    Must be zero     code
2=warning
3=error
A typical own exception code sent by Raise might therefore be &hE0000100 (error, application, code=&h100).

Example:

Code: Select all

#Include "windows.bi"
#Include "exceptions.bi"

Declare Sub Test1()
Declare Sub Test2()

Print "Before Test1()"
Test1()
Print "After Test1()"

Try(orange)
	Print "in Try(orange)"
	Print "Before Test2()"
	Test2()
Catch(orange, &hE0012345 __ &hE0012346)
	Print "Error E0012345 or E0012346 occured! Caught by orange."
EndTry(orange)
Print "After Test2()"

Sleep

Sub Test1()
	Try(apple)
		Print "in Try(apple)"
		Print "now let's create an access violation!"
		Print "Accessing invalid pointer!"
		Dim x As Integer Ptr = 222
		*x = 0
		Print "This text should never appear!"
	Catch(apple, EXCEPTION_INT_DIVIDE_BY_ZERO)
		Print "Divide by 0! This text should never appear!"
	Catch(apple, EXCEPTION_ACCESS_VIOLATION)
		Print "Oh no, an exception!"
		Print "code: " & Hex(ECode)
		Print "at address: " & Hex(EAddress)
	Catch(apple, Else)
		Print "all other exceptions would be caught here"
	Finally(apple)
		Print "This code will always be executed, regardless wheter an exception occured or not."
		Print "Here could be written ""Close #1"" or ""Delete foo""."
	EndTry(apple)
	
	Print "Now out of Try(apple)!"
	
	Do
		Try(pear)
			Print "in Try(pear)"
			Print "now we'll leave the Try block as well as the Do block around it, using ""Exit Do""."
			Print "before doing so, we'll call ""Leave(pear)"", so the ""Finally"" code gets executed."
			Leave(pear): Exit Do
			Print "This text should never appear!"
		Finally(pear)
			Print "in Finally(pear)!"
		EndTry(pear)
	Loop
	
	Print "Now out of Try(pear)!"
End Sub

Sub Test2()
	Try(peach)
		Print "in Try(peach)"
		Print "let's throw exception E0012345"
		Print "but peach only catches EXCEPTION_INT_DIVIDE_BY_ZERO!"
		Print "so it should be forwarded to orange"
		Raise(&hE0012345)
		Print "This text should never appear!"
	Catch(peach, EXCEPTION_INT_DIVIDE_BY_ZERO)
		Print "Divide by 0! This text should never appear!"
	Finally(peach)
		Print "in Finally(peach)"
	EndTry(peach)
	Print "out of Try(peach)"
End Sub
Now here the exceptions.bi file:

Code: Select all

#Ifndef EXCEPTION_RECORD
    #Error Please include windows.bi first!
#EndIf
#Ifndef __FB_WIN32__
    #Error Operating system not supported!
#EndIf

#Macro __trycheck(_name_)
    #Ifndef __eh_##_name_##_try_set
        #Error Undefined Try block name: _name_
    #EndIf
#EndMacro

#Macro Try(_name_)
    #Ifdef __eh_##_name_##_try_set2
        #Error Duplicated definition: _name_
    #EndIf
    #Define __eh_##_name_##_try_set
    #Define __eh_##_name_##_try_set2
    Scope
        Dim As Byte __eh_##_name_##_doret = 0
        Dim As Any Ptr __eh_##_name_##_ehesp = 0
        Dim As Byte __eh_##_name_##_ehandled = 0
        Dim As Byte __eh_##_name_##_wasincatch = 0
        Dim As EXCEPTION_RECORD Ptr __eh_##_name_##_erec = 0
        Dim As Byte __eh_##_name_##_erecarr(SizeOf(EXCEPTION_RECORD))
        Dim As Byte __eh_##_name_##_econarr(SizeOf(CONTEXT))
        Asm
            __eh_##_name_##_try:
            pushad
            mov esi, offset __eh_##_name_##_except
            push esi
            push dword ptr fs:[0]
            mov dword ptr fs:[0], esp        
        End Asm
        Scope
#EndMacro

#Macro __catch_codestart(_name_)
        End Scope
        #Define __eh_##_name_##_except_set
        Asm
            jmp __eh_##_name_##_finally_continue
            __eh_##_name_##_except:
            mov eax, [esp + 4]
            mov eax, [eax + 4]
            cmp eax, 2
            jne __eh_##_name_##_nounwind
            mov byte ptr [__eh_##_name_##_doret], 1
            call __eh_##_name_##_finally
            mov eax, 1
            ret
            __eh_##_name_##_nounwind:
            mov eax, esp
            mov esp, [esp + 8]
            pop dword ptr fs:[0]
            mov [esp], eax
            add esp, 4
            popad
            mov eax, [esp - 36]
            mov [__eh_##_name_##_ehesp], eax
            mov edx, eax         
        End Asm
        Scope
            Dim As EXCEPTION_RECORD Ptr ERecord
            Dim As CONTEXT Ptr EContext
            #Define ECode (ERecord->ExceptionCode)
            #Define EAddress (ERecord->ExceptionAddress)
            #Define ENumParams (ERecord->NumberParameters)
            #Define EParamsPtr CPtr(Integer Ptr, (@ERecord->ExceptionInformation(0)))
            Asm
                push edx
                mov edx, [edx + 8]
                mov [EContext], edx
                pop edx
                mov edx, [edx + 4]
                mov [ERecord], edx
            End Asm
            #Define __ersize (SizeOf(EXCEPTION_RECORD) + ERecord->NumberParameters * 4 - 4)
            #Define __erarrsize (UBound(__eh_##_name_##_erecarr) + 1) 
            CopyMemory(Cast(Any Ptr, @__eh_##_name_##_erecarr(0)), Cast(Any Ptr, ERecord), IIf(__ersize <= __erarrsize, __ersize, __erarrsize))
            #Undef __erarrsize
            #Undef __ersize
            ERecord = Cast(EXCEPTION_RECORD Ptr, @__eh_##_name_##_erecarr(0))
            __eh_##_name_##_erec = ERecord
            CopyMemory(Cast(Any Ptr, @__eh_##_name_##_econarr(0)), Cast(Any Ptr, EContext), UBound(__eh_##_name_##_econarr) + 1)
            EContext = Cast(CONTEXT Ptr, @__eh_##_name_##_econarr(0))
            Select Case ECode            
#EndMacro

#Define EParams(_i_) IIf(EParamsPtr <> 0 AndAlso _i_ < ENumParams, (EParamsPtr)[_i_], 0)

#Macro __catch_next(_name_, _ecode_)
                #Define __ ,
                Case _ecode_
                #Undef __
                    Asm mov byte ptr [__eh_##_name_##_ehandled], 1
                    Asm mov byte ptr [__eh_##_name_##_wasincatch], 1
#EndMacro

#Macro Catch(_name_, _ecode_)
    __trycheck(_name_)
    #Ifdef __eh_##_name_##_finally_set
        #Error Using Catch after Finally not allowed!
    #EndIf
    #Ifndef __eh_##_name_##_except_set
        __catch_codestart(_name_)
    #EndIf
    __catch_next(_name_, _ecode_)
#EndMacro

#Macro Finally(_name_)
        #Ifdef __eh_##_name_##_finally_set
            #Error Multiple Finally statements not allowed!
        #EndIf
        __trycheck(_name_)
        #Ifndef __eh_##_name_##_except_set
            Catch(_name_, 0)
                __eh_##_name_##_ehandled = 0
        #EndIf
            End Select
        End Scope
        #Define __eh_##_name_##_finally_set
        Asm
            mov byte ptr [__eh_##_name_##_doret], 0
            __eh_##_name_##_finally:
        End Asm
        Scope
            Select Case 0
                Case 0
#EndMacro

#Macro Leave(_name_)
        __trycheck(_name_)
        Asm
            mov byte ptr [__eh_##_name_##_doret], 1
            call __eh_##_name_##_finally
        End Asm
#EndMacro

#Macro EndTry(_name_)
            __trycheck(_name_)
            #Ifndef __eh_##_name_##_except_set
                Catch(_name_, 0)
                    __eh_##_name_##_ehandled = 0
            #EndIf
            #Ifndef __eh_##_name_##_finally_set
                Finally(_name_)
            #EndIf
            End Select
        End Scope
        Asm
            cmp byte ptr [__eh_##_name_##_doret], 0
            je __eh_##_name_##_noret
            ret
            __eh_##_name_##_noret:
            cmp byte ptr [__eh_##_name_##_ehandled], 0
            jne __eh_##_name_##_continue
            mov esp, [__eh_##_name_##_ehesp]
        End Asm
        RaiseException(__eh_##_name_##_erec->ExceptionCode, __eh_##_name_##_erec->ExceptionFlags, __eh_##_name_##_erec->NumberParameters, @__eh_##_name_##_erec->ExceptionInformation(0))
        Asm
            int3
            __eh_##_name_##_finally_continue:
            mov byte ptr [__eh_##_name_##_doret], 1
            call __eh_##_name_##_finally
            pop dword ptr fs:[0]
            add esp, 36
            jmp __eh_##_name_##_end_try
            __eh_##_name_##_continue:
            cmp byte ptr [__eh_##_name_##_wasincatch], 1
            je __eh_##_name_##_end_try
            pop dword ptr fs:[0]
            add esp, 36
            __eh_##_name_##_end_try:
        End Asm
    End Scope
    #Undef __eh_##_name_##_try_set
#EndMacro

#Macro ExitTry(_name_)
    __trycheck(_name_)
    Leave(_name_)
    Asm jmp __eh_##_name_##_end_try
#EndMacro

#Macro RaiseParam(_ecode_, _nump_, _params_)
    Scope
        #Define __ ,
        Dim __params(_nump_ - 1) As Integer = {_params_}
        #Undef __
        RaiseException(_ecode_, 0, _nump_, @__params(0))
    End Scope
#EndMacro

#Macro Raise(_ecode_)
    RaiseException(_ecode_, 0, 0, 0)
#EndMacro
Have fun!

greetings, Cherry
Last edited by Cherry on Sep 18, 2014 12:19, edited 6 times in total.
Cherry
Posts: 358
Joined: Oct 23, 2007 12:06
Location: Austria
Contact:

Post by Cherry »

I wonder why nobody is interested in this.
Mysoft
Posts: 836
Joined: Jul 28, 2005 13:56
Location: Brazil, Santa Catarina, Indaial (ouch!)
Contact:

Post by Mysoft »

Code: Select all

#define XLAB 9
#macro IncXLAB(AA,BB)  
  #undef XLAB  
  #define XLAB AA##BB
#endmacro
#macro Label(MYLAB)    
  LABLAB##MYLAB:
#endmacro
#macro GoLabel(MYLAB)
  goto LABLAB##MYLAB
#endmacro
#macro MyBegin()
  IncXLAB(XLAB,1)
  Label(XLAB)
#endmacro
#macro MyEnd()
  golabel(XLAB)
#endmacro

'--------------------------------------
MyBegin()
sleep 100
print "A";
if inkey$ <> "" goto Loop2
MyEnd()
'--------------------------------------
Loop2:
MyBegin()
sleep 100
print "B";
if inkey$ <> "" goto Loop3
MyEnd()
'--------------------------------------
Loop3:
maybe that could help you with auto generate label...
Mysoft
Posts: 836
Joined: Jul 28, 2005 13:56
Location: Brazil, Santa Catarina, Indaial (ouch!)
Contact:

Post by Mysoft »

ok, it seems that it work ok... personally i dont like the try catch method... but the implementation seems simple enough, i wonder why something like that couldn't be turned into a simple "on error goto xxx" "on error goto 0" to be compatible with basic style... and prevent unecessary "crashes"

that's a good thing to for using with my "freebasic" expansions... like if you want to add compiled freebasic programs for some spreadsheet, you can't allow it to crash... cuz that will be pretty bad :P
Cherry
Posts: 358
Joined: Oct 23, 2007 12:06
Location: Austria
Contact:

Post by Cherry »

Mysoft wrote:

Code: Select all

#define XLAB 9
#macro IncXLAB(AA,BB)  
  #undef XLAB  
  #define XLAB AA##BB
#endmacro
#macro Label(MYLAB)    
  LABLAB##MYLAB:
#endmacro
#macro GoLabel(MYLAB)
  goto LABLAB##MYLAB
#endmacro
#macro MyBegin()
  IncXLAB(XLAB,1)
  Label(XLAB)
#endmacro
#macro MyEnd()
  golabel(XLAB)
#endmacro

'--------------------------------------
MyBegin()
sleep 100
print "A";
if inkey$ <> "" goto Loop2
MyEnd()
'--------------------------------------
Loop2:
MyBegin()
sleep 100
print "B";
if inkey$ <> "" goto Loop3
MyEnd()
'--------------------------------------
Loop3:
maybe that could help you with auto generate label...
#

That's nice, but there is a limit in the lenght of labels...

Plus, the second problem are nestes Try's. I would need a stack, or an array and inc/dec for #defines...
Mysoft wrote:ok, it seems that it work ok... personally i dont like the try catch method... but the implementation seems simple enough, i wonder why something like that couldn't be turned into a simple "on error goto xxx" "on error goto 0" to be compatible with basic style... and prevent unecessary "crashes"

that's a good thing to for using with my "freebasic" expansions... like if you want to add compiled freebasic programs for some spreadsheet, you can't allow it to crash... cuz that will be pretty bad :P
This is not the ultimative shield ^^. A simple

Code: Select all

Asm
    xor eax, eax
    mov fs:[eax], eax ' overwrite the exception handler with 0
    mov [eax], eax ' raise exception -> program will jump to address 0!
End Asm
inside a Try block will crash the program immediately unless an UnhandledExceptionFilter is installed (this is not done by this macros).

greetings, Cherry
Hexadecimal Dude!
Posts: 360
Joined: Jun 07, 2005 20:59
Location: england, somewhere around the middle
Contact:

Post by Hexadecimal Dude! »

You can get a stack in the PP by combining Mysoft's method with some stuff from fb extended library:

Code: Select all

#include "ext/preprocessor/seq.bi"

#define mystack (EMPTY)

#macro stackpush(_stack,_value) 
  #undef mystack
  #define mystack fbextPP_SeqPushBack(_stack,_value)
#endmacro

#macro stackpop(_stack)
  #undef mystack
  #define mystack fbextPP_SeqPopBack(_stack)
#endmacro

stackpush(mystack,"foo")
#print mystack
stackpush(mystack,"bar")
#print mystack
stackpop(mystack)
#print mystack
stackpush(mystack,"baz")
#print mystack
stackpop(mystack)
#print mystack
stackpop(mystack)
#print mystack
P.S. good work having written try..catch..finally for windows, although I use linux so that's why I didn't look at this thread earlier. Hopefull you inspire someone to write some implementations for other platforms and so we can have error checking in fbc!
Cherry
Posts: 358
Joined: Oct 23, 2007 12:06
Location: Austria
Contact:

Post by Cherry »

Yes, I have installed FBExt in <compiler path>\inc\ext. Output:

Code: Select all

D:\FreeBasic\_Compiler\fbc -s console "FbTemp.bas"
fbextPP_SeqPushBack((EMPTY),"foo")
fbextPP_SeqPushBack(fbextPP_SeqPushBack((EMPTY),"foo"),"bar")
fbextPP_SeqPopBack(fbextPP_SeqPushBack(fbextPP_SeqPushBack((EMPTY),"foo"),"bar"))
fbextPP_SeqPushBack(fbextPP_SeqPopBack(fbextPP_SeqPushBack(fbextPP_SeqPushBack((EMPTY),"foo"),"bar")),"baz")
fbextPP_SeqPopBack(fbextPP_SeqPushBack(fbextPP_SeqPopBack(fbextPP_SeqPushBack(fbextPP_SeqPushBack((EMPTY),"foo"),"bar")),"baz"))
fbextPP_SeqPopBack(fbextPP_SeqPopBack(fbextPP_SeqPushBack(fbextPP_SeqPopBack(fbextPP_SeqPushBack(fbextPP_SeqPushBack((EMPTY),"foo"),"bar")),"baz")))

Make done
Sorry, I have no idea how this could help me...

I would need a way to increase a counter, push the new value at each Try statement and assign it to _name_, and pop the old value at each EndTry statement.
Last edited by Cherry on Jun 07, 2009 17:51, edited 1 time in total.
Hexadecimal Dude!
Posts: 360
Joined: Jun 07, 2005 20:59
Location: england, somewhere around the middle
Contact:

Post by Hexadecimal Dude! »

do you have fbext installed?
Cherry
Posts: 358
Joined: Oct 23, 2007 12:06
Location: Austria
Contact:

Post by Cherry »

Oh, old version of FBExt ;)

Now it's this:

Code: Select all

D:\FreeBasic\_Compiler\fbc -s console "FbTemp.bas"
(EMPTY)("foo")
(EMPTY)("foo")("bar")
(EMPTY)("foo")
(EMPTY)("foo")("baz")
(EMPTY)("foo")
(EMPTY)

Make done
1. Where will the values be popped to?
2. How can I create a counter now?

My explicit problem is described above.

:(
Cherry
Posts: 358
Joined: Oct 23, 2007 12:06
Location: Austria
Contact:

Post by Cherry »

I just realized that the "+ &hC000" at "Dim As Byte __eh_##_name_##_erecarr(SizeOf(EXCEPTION_RECORD) + &hC000)" is senseless and only leads to a stack overflow at too much nested Try blocks. I think I added this because I didn't know that the number of exception parameters is fixed.

Thus I removed it now.
Cherry
Posts: 358
Joined: Oct 23, 2007 12:06
Location: Austria
Contact:

Post by Cherry »

Fixed a critical bug.

After some exceptions, the whole program could crash or data could be corrupted because too much of the stack was released.
4quiles
Posts: 56
Joined: Jul 19, 2009 3:19

Re: [Win32] Exception handling using Try/Catch/Finally

Post by 4quiles »

Just to say a big thank you for this code.
Cherry
Posts: 358
Joined: Oct 23, 2007 12:06
Location: Austria
Contact:

Re: [Win32] Exception handling using Try/Catch/Finally

Post by Cherry »

You are welcome,

however I just realized that there is obviously some kind of bug in the new [ code ] feature in the forums - at the end of my code I found this:

&#123;__params__&#125;

this should be {__params__} and I definitely didn't put the HTML entities there...

EDIT: Wow, clicking "Download" makes it worse - then you get LF line endings instead of CRLF, and instead of normal ( and ) there is &#40; and &#41; everywhere!
vdecampo
Posts: 2992
Joined: Aug 07, 2007 23:20
Location: Maryland, USA
Contact:

Re: [Win32] Exception handling using Try/Catch/Finally

Post by vdecampo »

Awesome!

-Vince
sean_vn
Posts: 283
Joined: Aug 06, 2012 8:26

Re: [Win32] Exception handling using Try/Catch/Finally

Post by sean_vn »

I always found try,catch,finally problematic in java. It seems your version is more controllable. In programming languages that can return more than one value the last value can be an error indicator: a,e=div(x,y) where e would indicate a divide by zero and if you don't need to know then just a=div(x,y). Sticky errors would be useful in FreeBasic especially where you are doing lots of memory allocation, though in that case you should really be using memory mapped files.
Post Reply