Possibly error message when starting program with missing included DLL
- ArneOrtlinghaus
- Posts: 412
- Joined: Tue Nov 10, 2015 7:48 am
- Location: Italy
Possibly error message when starting program with missing included DLL
Hi all,
thank you for responding.
I understand that a messagebox in general can disturb usage of different GUIs. Having the possibility for a callback function for trapping such an error could be a solution. The name Wolfgang proposed would be ok.
Of course normally it should not happen, that a Dll is missing and in the last years at our customer sites this happens only randomly. Possible reasons can be for example an Antivirus, that blocks reading, copying files or deletes them (or an admin that tries to clean an infected fileserver). For this case such an error message simplifies diagnose time. Unfortunately the event log entry currently does not give an indication which file could have generated the exception. Using Process Monitor from Sysinternals showed the missing file, but it takes much time using such a tool and analyzing it.
thank you for responding.
I understand that a messagebox in general can disturb usage of different GUIs. Having the possibility for a callback function for trapping such an error could be a solution. The name Wolfgang proposed would be ok.
Of course normally it should not happen, that a Dll is missing and in the last years at our customer sites this happens only randomly. Possible reasons can be for example an Antivirus, that blocks reading, copying files or deletes them (or an admin that tries to clean an infected fileserver). For this case such an error message simplifies diagnose time. Unfortunately the event log entry currently does not give an indication which file could have generated the exception. Using Process Monitor from Sysinternals showed the missing file, but it takes much time using such a tool and analyzing it.
Possibly error message when starting program with missing included DLL
Guys,
I have given this some thought and a special function name will not be the way to go.
This function will become part of the compiler generated static functions class and that means that the static constructor for this class will have to be called before the code in the special function can be executed. This static constructor contains code to initialize defines that do not have a compile time constant (such as defines that have a symbol as value, eg DEFINE SomeValue := #SomeSymbol) and the code to initialize globals that are declared with an initializer (GLOBAL aValues[0] AS ARRAY).
There is a big risk that some of this initialization code will depend on external DLLs that may or may not be correctly initialized.
However there is a better way to do this and it is already built into the compiler.
This is the compiler option -Main.
Consider the following code:
This code will generate an exception at startup:
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:XIDEProjectsDefaultApplicationsApplication1PrgStart.prg:line 4
--- End of inner exception stack trace ---
at Application1.Exe.Functions.Start()
And you cannot intercept this.
Now add the following code:
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
Of course you can also register an UnHandledException handler in the AppDomain class inside the new startup code. Change the code to:
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.
I hope this helps.
Robert
I have given this some thought and a special function name will not be the way to go.
This function will become part of the compiler generated static functions class and that means that the static constructor for this class will have to be called before the code in the special function can be executed. This static constructor contains code to initialize defines that do not have a compile time constant (such as defines that have a symbol as value, eg DEFINE SomeValue := #SomeSymbol) and the code to initialize globals that are declared with an initializer (GLOBAL aValues[0] AS ARRAY).
There is a big risk that some of this initialization code will depend on external DLLs that may or may not be correctly initialized.
However there is a better way to do this and it is already built into the compiler.
This is the compiler option -Main.
Consider the following code:
Code: Select all
GLOBAL x AS INT
GLOBAL y := 1 / x AS INT
FUNCTION Start AS VOID
? "Function Start"
RETURN
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:XIDEProjectsDefaultApplicationsApplication1PrgStart.prg:line 4
--- End of inner exception stack trace ---
at Application1.Exe.Functions.Start()
And you cannot intercept this.
Now add the following code:
Code: Select all
CLASS MyStartupCode
STATIC METHOD Start AS VOID
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
Of course you can also register an UnHandledException handler in the AppDomain class inside the new startup code. Change the code to:
Code: Select all
CLASS MyStartupCode
STATIC METHOD Start AS VOID
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
e := (Exception) args:ExceptionObject
// 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 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.
I hope this helps.
Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
The Netherlands
robert@xsharp.eu
Possibly error message when starting program with missing included DLL
Hi Robert,
thank you very much - this is great!
I will build a sample later today.
Wolfgang
thank you very much - this is great!
I will build a sample later today.
Wolfgang
Wolfgang Riedmann
Meran, South Tyrol, Italy
wolfgang@riedmann.it
https://www.riedmann.it - https://docs.xsharp.it
Meran, South Tyrol, Italy
wolfgang@riedmann.it
https://www.riedmann.it - https://docs.xsharp.it
- ArneOrtlinghaus
- Posts: 412
- Joined: Tue Nov 10, 2015 7:48 am
- Location: Italy
Possibly error message when starting program with missing included DLL
Great, it works as I have hoped and this without having to wait for updates, thank you!
I had to modify the code a little bit as below (adding the word strict and using a message box, because the console window did not appear on the screen.
CLASS MyStartupCode
STATIC METHOD Start AS VOID strict
local c as string
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
radixdn.exe.Functions.Start()
CATCH e AS Exception
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 (null_ptr,string2psz(c), string2psz("RADIX"), MB_OK + MB_ICONSTOP+ MB_DEFAULT_DESKTOP_ONLY + MB_TOPMOST)
// We should probably log this to disk as well !
END TRY
RETURN
END CLASS
I had to modify the code a little bit as below (adding the word strict and using a message box, because the console window did not appear on the screen.
CLASS MyStartupCode
STATIC METHOD Start AS VOID strict
local c as string
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
radixdn.exe.Functions.Start()
CATCH e AS Exception
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 (null_ptr,string2psz(c), string2psz("RADIX"), MB_OK + MB_ICONSTOP+ MB_DEFAULT_DESKTOP_ONLY + MB_TOPMOST)
// We should probably log this to disk as well !
END TRY
RETURN
END CLASS
Possibly error message when starting program with missing included DLL
Hi Arne,
if you like to open a console window from a non-console application (WPF, Windows Forms, VO GUI) you need that here:
https://stackoverflow.com/questions/436 ... pplication
Wolfgang
if you like to open a console window from a non-console application (WPF, Windows Forms, VO GUI) you need that here:
https://stackoverflow.com/questions/436 ... pplication
Wolfgang
Wolfgang Riedmann
Meran, South Tyrol, Italy
wolfgang@riedmann.it
https://www.riedmann.it - https://docs.xsharp.it
Meran, South Tyrol, Italy
wolfgang@riedmann.it
https://www.riedmann.it - https://docs.xsharp.it
Possibly error message when starting program with missing included DLL
Arne,
Adding Strict() is needed because you have enabled the compiler option /vo5 (implicit Clipper calling convention).
My sample did not have that.
Robert
Adding Strict() is needed because you have enabled the compiler option /vo5 (implicit Clipper calling convention).
My sample did not have that.
Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
The Netherlands
robert@xsharp.eu
Possibly error message when starting program with missing included DLL
Arne,
One more thing:
you are using String2Psz() in your exception handler.
I would not recommend that, since this relies on the runtime and this will fail when one of the X# runtime DLLs is missing.
To avoid that declare MessageBox yourself without PSZ type and add it as method to your MyStartupCode class
Do not make this a function, because that will again fail when the type initializer of the Functions class fails...
Robert
One more thing:
you are using String2Psz() in your exception handler.
I would not recommend that, since this relies on the runtime and this will fail when one of the X# runtime DLLs is missing.
To avoid that declare MessageBox yourself without PSZ type and add it as method to your MyStartupCode class
Code: Select all
// add this line to the start of your PRG
USING System.Runtime.InteropServices
// add this to your MyStartupCode class
[DllImport("user32.dll", CharSet := CharSet.Ansi)];
STATIC METHOD MessageBox(hwnd AS IntPtr, lpText AS STRING, lpCaption AS STRING, uType AS DWORD) AS INT PASCAL
Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
The Netherlands
robert@xsharp.eu
- ArneOrtlinghaus
- Posts: 412
- Joined: Tue Nov 10, 2015 7:48 am
- Location: Italy
Possibly error message when starting program with missing included DLL
Robert,
this is something interesting, which I do not understand, why it can work.
The MessageBox in user32 surely is the C-base function with pointers to 0-terminated C-Strings. Here you declare the parameters using the Dotnet-Variable type "STRING" which is an object. Who cares for the conversion of this object when even with this interface you give the compiler hints that seem not to fit?
STATIC METHOD MessageBox(hwnd AS IntPtr, lpText AS STRING, lpCaption AS STRING, uType AS DWORD) AS INT PASCAL
int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
this is something interesting, which I do not understand, why it can work.
The MessageBox in user32 surely is the C-base function with pointers to 0-terminated C-Strings. Here you declare the parameters using the Dotnet-Variable type "STRING" which is an object. Who cares for the conversion of this object when even with this interface you give the compiler hints that seem not to fit?
STATIC METHOD MessageBox(hwnd AS IntPtr, lpText AS STRING, lpCaption AS STRING, uType AS DWORD) AS INT PASCAL
int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
Possibly error message when starting program with missing included DLL
Arne,
The method is marked with the [DllImport] attribute. This tells the runtime that it is a native DLL.
The runtime "knows" that it needs to do some work on the strings.
The CharSet := CharSet.Ansi tells the .Net runtime that the managed strings need to be converted from Unicode to Ansi.
This is all managed "magically" by the .Net runtime.
You can also control this behavior by using a MarshalAs attribute
You could therefore also write
[DllImport("user32.dll"];
STATIC METHOD MessageBox(hwnd AS IntPtr, [MarshalAs(UnmanagedType.LPStr)] lpText AS STRING, [MarshalAs(UnmanagedType.LPStr)] lpCaption AS STRING, uType AS DWORD) AS INT PASCAL
LIkewise you can also pass a stringbuilder to methods in the win32 api that return strings (such as GetWindowText)
See https://docs.microsoft.com/en-us/dotnet ... or-strings
Robert
The method is marked with the [DllImport] attribute. This tells the runtime that it is a native DLL.
The runtime "knows" that it needs to do some work on the strings.
The CharSet := CharSet.Ansi tells the .Net runtime that the managed strings need to be converted from Unicode to Ansi.
This is all managed "magically" by the .Net runtime.
You can also control this behavior by using a MarshalAs attribute
You could therefore also write
[DllImport("user32.dll"];
STATIC METHOD MessageBox(hwnd AS IntPtr, [MarshalAs(UnmanagedType.LPStr)] lpText AS STRING, [MarshalAs(UnmanagedType.LPStr)] lpCaption AS STRING, uType AS DWORD) AS INT PASCAL
LIkewise you can also pass a stringbuilder to methods in the win32 api that return strings (such as GetWindowText)
See https://docs.microsoft.com/en-us/dotnet ... or-strings
Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
The Netherlands
robert@xsharp.eu
- ArneOrtlinghaus
- Posts: 412
- Joined: Tue Nov 10, 2015 7:48 am
- Location: Italy
Possibly error message when starting program with missing included DLL
If someone needs the [STAThread] attribute for Internet Explorer control/Olecontrols, it is important to position it before the first method called, in this case the new Start method and not the start function.
CLASS MyStartupCode
[STAThread] ;
STATIC METHOD Start AS VOID strict
CLASS MyStartupCode
[STAThread] ;
STATIC METHOD Start AS VOID strict