Possibly error message when starting program with missing included DLL

Have some feedback and input to share?
Don't be shy and drop us a note. We want to hear from you and strive to make our site better and more user friendly for our guests and members a like.
User avatar
ArneOrtlinghaus
Posts: 412
Joined: Tue Nov 10, 2015 7:48 am
Location: Italy

Possibly error message when starting program with missing included DLL

Post by ArneOrtlinghaus »

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.
User avatar
robert
Posts: 4518
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Possibly error message when starting program with missing included DLL

Post by robert »

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:

Code: Select all

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:

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
User avatar
wriedmann
Posts: 3754
Joined: Mon Nov 02, 2015 5:07 pm
Location: Italy

Possibly error message when starting program with missing included DLL

Post by wriedmann »

Hi Robert,
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
User avatar
ArneOrtlinghaus
Posts: 412
Joined: Tue Nov 10, 2015 7:48 am
Location: Italy

Possibly error message when starting program with missing included DLL

Post by ArneOrtlinghaus »

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.
missingdll.png
missingdll.png (11.28 KiB) Viewed 581 times

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
User avatar
wriedmann
Posts: 3754
Joined: Mon Nov 02, 2015 5:07 pm
Location: Italy

Possibly error message when starting program with missing included DLL

Post by wriedmann »

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
Wolfgang Riedmann
Meran, South Tyrol, Italy
wolfgang@riedmann.it
https://www.riedmann.it - https://docs.xsharp.it
User avatar
robert
Posts: 4518
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Possibly error message when starting program with missing included DLL

Post by robert »

Arne,

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
User avatar
robert
Posts: 4518
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Possibly error message when starting program with missing included DLL

Post by robert »

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

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
Do not make this a function, because that will again fail when the type initializer of the Functions class fails...



Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
User avatar
ArneOrtlinghaus
Posts: 412
Joined: Tue Nov 10, 2015 7:48 am
Location: Italy

Possibly error message when starting program with missing included DLL

Post by ArneOrtlinghaus »

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)
User avatar
robert
Posts: 4518
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Possibly error message when starting program with missing included DLL

Post by robert »

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
XSharp Development Team
The Netherlands
robert@xsharp.eu
User avatar
ArneOrtlinghaus
Posts: 412
Joined: Tue Nov 10, 2015 7:48 am
Location: Italy

Possibly error message when starting program with missing included DLL

Post by ArneOrtlinghaus »

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
Post Reply