xsharp.eu • Possibly error message when starting program with missing included DLL - Page 2
Page 2 of 5

Possibly error message when starting program with missing included DLL

Posted: Thu Mar 19, 2020 7:58 am
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.

Possibly error message when starting program with missing included DLL

Posted: Thu Mar 19, 2020 8:49 am
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

Possibly error message when starting program with missing included DLL

Posted: Thu Mar 19, 2020 9:02 am
by wriedmann
Hi Robert,
thank you very much - this is great!
I will build a sample later today.
Wolfgang

Possibly error message when starting program with missing included DLL

Posted: Thu Mar 19, 2020 9:27 am
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 576 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

Possibly error message when starting program with missing included DLL

Posted: Thu Mar 19, 2020 9:40 am
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

Possibly error message when starting program with missing included DLL

Posted: Thu Mar 19, 2020 11:20 am
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

Possibly error message when starting program with missing included DLL

Posted: Thu Mar 19, 2020 11:29 am
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

Possibly error message when starting program with missing included DLL

Posted: Thu Mar 19, 2020 2:56 pm
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)

Possibly error message when starting program with missing included DLL

Posted: Thu Mar 19, 2020 3:37 pm
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

Possibly error message when starting program with missing included DLL

Posted: Fri Mar 20, 2020 8:39 am
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