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
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
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
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
greetings, Cherry