Sometimes your program throws runtime errors at startup. These can be caused by missing assemblies and or by errors in initialization code.
These errors can be difficult to trap, since the errors occur inside code that is executed before the first line of code in your application.
Take the following example
GLOBAL x AS INT
GLOBAL y := 1 / x AS INT
FUNCTION Start AS VOID
? "Function Start"
RETURN
This code will generate an exception at startup, which you can only see/read when you run the app from the commandline
Unhandled Exception: System.TypeInitializationException: The type initializer for 'Application1.Exe.Functions' threw an exception. ---> System.DivideByZeroException: Attempted to divide by zero.
at Application1.Exe.Functions..cctor() in C:\XIDE\Projects\Default\Applications\Application1\Prg\Start.prg:line 4
--- End of inner exception stack trace ---
at Application1.Exe.Functions.Start()
The normal program flow in an X# application is this:
•Your application is started
•The DotNet framework is initialized
•The entrypoint is called. The normal entrypoint in an app is the Start function, which is converted by the compiler to a Start method in a compiler generated Functions class.
The same class also has the globals and defines from your app. If one of these globals or defines contains an initialization expression that cannot be resolved at compile then these this code will be executed in the static constructor of the Functions class (in the error message above this is called the "type initializer").
•The code above clearly makes a mistake which causes a divide by zero error.
To intercept this we would like to run some other code at startup and add a try .. catch construct to make sure we can catch this kind of errors.
Add the following code:
CLASS MyStartupCode
STATIC METHOD Start AS VOID STRICT
TRY
// Note that in the following line the name before .Exe must
// match the file name of your EXE. In my case I am generating Application1.exe
Application1.Exe.Functions.Start()
CATCH e AS Exception
// We should probably log this to disk as well !
Console.WriteLine("An unhandled exception has occurred")
Console.WriteLine("===================================")
DO WHILE e != NULL
Console.WriteLine("Exception: "+e:Message)
Console.WriteLine("Callstack:")
Console.WriteLine(e:StackTrace)
Console.WriteLine()
e := e:InnerException
ENDDO
Console.WriteLine("===================================")
Console.WriteLine("Press any to close the application")
Console.ReadLine()
END TRY
RETURN
END CLASS
You may have to change the call to Application1.Exe.Functions.Start() into something that matches your EXE name.
Now goto the General page in the application properties in VS and at the entry "Startup Object" set the value MyStartupCode.
In XIDE add the command line option -main:MyStartupCode:
and run the code again.
The error is now trapped and shown.
If you app is not a Console app but a Windows app then the console output may not be visible.
Of course you can also register an UnHandledException handler in the AppDomain class inside the new startup code.
Change the code to:
CLASS MyStartupCode
STATIC METHOD Start AS VOID STRICT
TRY
System.AppDomain.CurrentDomain:UnhandledException += ExceptionHandler
Application1.Exe.Functions.Start()
CATCH e AS Exception
ExceptionHandler(NULL, UnhandledExceptionEventArgs{e, TRUE})
END TRY
RETURN
STATIC METHOD ExceptionHandler( sender AS OBJECT, args AS UnhandledExceptionEventArgs) AS VOID
LOCAL e AS Exception
LOCAL c AS STRINGe := (Exception) args:ExceptionObject
c := "An unhandled exception has occurred"+crlf
c += "==================================="+crlf
DO WHILE e != NULL
c += "Exception: "+e:Message+crlf
c += "Callstack:"+crlf
c += e:StackTrace+crlf
e := e:InnerException
ENDDO
c += "==================================="+crlf
MessageBox(IntPtr.Zero, c,"Error",0x60010) // MB_OK + MB_ICONSTOP+ MB_DEFAULT_DESKTOP_ONLY + MB_TOPMOST
[DllImport("user32.dll", CharSet := CharSet.Ansi)];
STATIC METHOD MessageBox(hwnd AS IntPtr, lpText AS STRING, lpCaption AS STRING, uType AS DWORD) AS INT PASCAL
END CLASS
One remark:
Do NOT use or call any Xbase types and or functions in the exception handler, since you can't be sure that the runtime was initialized properly.
If you use classes written by yourself make sure that everything is strongly typed and uses native types only. So no USUAL, FLOAT, SYMBOL etc.
And do not call code inside functions in the same app or DLLs, because again the type initializers for the classes in which these functions are located can also throw exceptions.