Application process stays loaded after exit - vb.net

We have an application that allows custom UI and interaction via SDK.
A DLL is developed for this purpose using VB.Net and the SDK.
An object variable refers to the application and there are some other object variables for components within the application.
Application allows assigning VBScript code to button(s) displayed in toolbar. The VBScript code is:
Dim Utility_Main
Set Utility_Main = CreateObject("Utility.Application")
Utility_Main.Launch()
This launches a form (custom UI) and users can interact with the application via this form.
Although application itself has its own UI, this utility form is created for database lookup, preserving certain attributes of application objects etc.
In just about every exit point of the form, a procedure is called to unset object variables for application and its components using following code:
========================================
Try
Marshal.ReleaseComObject(objX)
Catch
End Try
Try
objX = Nothing
GC.Collect()
Catch
End Try
Note1: ReleaseComObject and setting object variable to Nothing was wrapped in "If (Not objX is Nothing) Then". But it was changed to above format to make sure it gets called.
Note2: GC.Collect was added later to force GC.
========================================
This is done for each object in reverse order of object hierarchy.
Application's executable (Application.exe) remains loaded in both of the following cases:
Application is closed first and then Utility form is closed
Utility form is closed first and then the application is closed
The only time "Application.exe" goes away is if Application is closed first and then Utility form is closed by clicking on "End Task" in Task Manager.
Any help will be greatly appreciated.

I realize you say that it was split up for some reason, but this should be:
Try
If objX IsNot Nothing Then
Marshal.ReleaseComObject(objX)
End If
Catch e As ArgumentException
' hopefully you have some debug output here
Finally
objX = Nothing
GC.Collect() ' really doubt this is necessary
End Try
This guarantees that objX is released if it is not Nothing, and if that throws an exception (note the list of exceptions), then you can catch it and figure out what happened. Whether or not an exception is thrown, objX will be set to Nothing and the GC will be invoked.
It's probably not this code that is causing your program to stay open. You will need to show more code (how they interact, or any non-daemon threads that you may manually start will also be to blame). It sounds like Utility is forcing the Application to stay alive with a non-daemon, background thread, but it really could be the other way around, and it could be the case that doing that in both directions.

Related

VB.NET catch error for ENTIRE application

Need help with some VB.net coding (NOT C or c++)
So far I'm using this code to catch errors for specific line(s) of codes:
Try
bla bla bla
Catch ex As Exception
msbox("Error: " & ex.message)
End Try
But sometimes the application stops due to an error where I don't have a catch; how do I on occasions like this call upon a specific Sub (catch the error) for ANY OTHER error in the ENTIRE application where the Sub will display the error message (where I also plan on sending my self an e-mail in that sub to notify me application has stopped)?
I'm not sure if it will conflict with all current Try/Catch commands in my application, but I would prefer to only catch the error on code that currently is not within a Catch handler.
Thank you so much!
This functionality is built into the VB application framework, which is enabled by default in WinForms applications. Open the Application page of the project properties, click the View Application Events button and add a handler for the UnhandledException event using the navigation bar at the top of the code window. Done!
The VB application framework hides some of the complexity of applications that you must implement yourself in C#. It includes a Startup event that you can handle instead of adding code to a Main method, a StartupNextInstance event to support single-instance apps with commandline arguments, multi-threaded splash screen functionality and more.
Regarding your emailing idea, just sure to add in a privacy notice before auto-emailing yourself anything in your apps; this can be a big bone of contention to users, & if an astute one catches it silently phoning home your rep is down the drain.
As for a global error handler, have a look here:
https://www.dotnetcurry.com/patterns-practices/1364/error-handling-dotnet-projects

Winform controls loaded from CefSharp causing Cross-Thread errors

I'm integrating CefSharp into a legacy Winforms application. Currently the application uses the default .NET browser control. Unfortunately, this control has a memory leak which is causing serious problems. I'm trying to get the CefSharp browser integrated without major refactoring, since the application is scheduled to be retired later this year and replaced with a new WPF application.
So far, I'm able to make CefSharp work in most scenarios. Standard webpages open without a problem. However, some of these pages have specially formed links which, when interpretted by the application, open up .Net forms instead of other webpages. This is where I run into an issue. When the webpage opened in CefSharp calls one of these links and the application attempts to open the new form, it appears to be doing so on the thread hosting the CefSharp instance, which is different from that hosting the application itself. This leads to numerous cross-thread issues (the legacy application in question isn't particularly well architectured). I'm trying to find a way to solve this issue without rearchitecturing the Winform application.
The following is a brief example of the situation.
Controls
frmMain
This is the primary form on the application. It has a number of duties, but the one pertinent to the current situation is that it hosts a Telerik DocumentTabStrip which contains the application's "tabs" (each browser or form opens within one of these tabs). It also contains the method that is used to load the various form or browser controls that are instantiated and add them to the aforementioned DocumentTabStrip.
ucChromeBrowser
This is the object which wraps the CefSharp browser instances which get created. It also has all the Event Handlers for the CefSharp events, such as IRequestHandler, ILifespanHandler, IContextMenuHandler, etc.
EntryScreens.uc_Foo
This is one of the Winform controls that are called from the webpage hosted in ucChromeBrowser. A typical link looks like WEB2WIN:Entryscreens.uc_Foo?AdditionalParameterDataHere. We intercept these calls in frmMain and instead of attempting to open the link in a browser, we instantiate a new instance of EntryScreen.uc_Foo and load that new form into frmMain.DocumentTabStrip as follows.
Dim _DockWindow As Telerik.WinControls.UI.Docking.DocumentWindow = Nothing
Dim _Control As ucBaseControl = Nothing
Dim _WebBrowser As ucBrowser = Nothing
Dim _isWebBrowerLink As Boolean = False
'Do Other Stuff here, such as instantiating the _Control or _WebBrowser instance, setting _isWebBrowserLink, etc.
_DockWindow = New Telerik.WinControls.UI.Docking.DocumentWindow
If _isWebBrowerLink Then
If Not IsNothing(_WebBrowser) Then
_WebBrowser.Dock = DockStyle.Fill
_DockWindow.Controls.Add(_WebBrowser)
End If
Else
_Control.Dock = DockStyle.Fill
_DockWindow.Controls.Add(_Control)
End If
DocumentTabStrip.InvokeIfRequired(Sub() DocumentTabStrip.Controls.Add(_DockWindow))
(In case it matters, here's the InvokeIfRequired method I'm calling.)
Public Module ISynchronizeInvokeExtensions
<Runtime.CompilerServices.Extension>
Public Sub InvokeIfRequired(obj As ISynchronizeInvoke, action As MethodInvoker)
Dim idleCounter As Integer = 0
While Not CType(obj, Control).Visible
'Attempt to sleep since there's no visible control
If idleCounter < 5 Then
Threading.Thread.Sleep(50)
Else
Exit While
End If
End While
If obj.InvokeRequired Then
Dim args = New Object(-1) {}
obj.Invoke(action, args)
Else
action()
End If
End Sub
End Module
The issue comes up when trying to call DocumentTabStrip.InvokeIfRequired(Sub() DocumentTabStrip.Controls.Add(_DockWindow)). From what I can tell, it appears that this changes the layout of the controls hosted within, causing various controls to be instantiated or to have their events called. This, in turn, causes an InvalidOperationException (e.g.: "Cross-thread operation not valid: Control 'pnlLoading' accessed from a thread other than the thread it was created on."). The specific child control varies from Form to Form (so for Form A it might always be pnlLoading which causes it, but for another Form it might be a different control). But most or all of them exhibit this behavior. I have no doubt it's due to the controls themselves being poorly designed, but I don't really have the time to refactor all of them.
So that's the situation. It appears as though the multi-threaded nature of CefSharp is conflicting with the single-threaded nature of the controls in question and causing them to get called on a different thread than they would be otherwise. Is there a way to prevent this?
Controls should only be created on the UI thread, and not in background threads. The error message tells you pretty clearly what's going on.
Cross-thread operation not valid: Control 'pnlLoading' accessed from a thread other than the thread it was created on.
You are accessing the control from the UI thread but due to that it is actually created in a background thread you are performing a cross-thread access since you're invoking to the wrong thread.
Whatever you do you would almost always have to invoke to the background thread when accessing the control, but you cannot do this for any automatic access done by, for instance, the UI message loop.
Therefore you should put all control creation and accessing in the UI only, thus meaning you must put all that code in your first code block in an invoke.
DocumentTabStrip.InvokeIfRequired( _
Sub()
Dim _DockWindow As Telerik.WinControls.UI.Docking.DocumentWindow = Nothing
Dim _Control As ucBaseControl = Nothing
Dim _WebBrowser As ucBrowser = Nothing
Dim _isWebBrowerLink As Boolean = False
'Do Other Stuff here, such as instantiating the _Control or _WebBrowser instance, setting _isWebBrowserLink, etc.
_DockWindow = New Telerik.WinControls.UI.Docking.DocumentWindow
If _isWebBrowerLink Then
If Not IsNothing(_WebBrowser) Then
_WebBrowser.Dock = DockStyle.Fill
_DockWindow.Controls.Add(_WebBrowser)
End If
Else
_Control.Dock = DockStyle.Fill
_DockWindow.Controls.Add(_Control)
End If
DocumentTabStrip.Controls.Add(_DockWindow)
End Sub)

Universal Exception Handler

Is there a simple way to catch all exceptions in your VB.NET applications? I'm interested in making it so that instead of my users seeing a runtime error, they just are told that an error occurred and to contact tech support, then the relevant error information is logged so our support team can look at it afterwards.
You can use the OnUnhandledException application event to catch (almost) every exception that wasn't handled by the code.
On the Project Properties window (double-click project file on the solution explorer or Project Menu -> [Project name] properties), the Application page has a "View Application Events" button that creates a new file in your project.
In that file there are some events that are fired at application level; one of those is the UnhandledException. Whatever you put there will be executed instead of the classic JIT dialog. The UnhandledExceptionEventArgs object has an Exception property with the unhandled exception object, and a ExitApplication property that decides if the application should exit or continue executing.
Namespace My
' The following events are available for MyApplication:
'
' Startup: Raised when the application starts, before the startup form is created.
' Shutdown: Raised after all application forms are closed. This event is not raised if the application terminates abnormally.
' UnhandledException: Raised if the application encounters an unhandled exception.
' StartupNextInstance: Raised when launching a single-instance application and the application is already active.
' NetworkAvailabilityChanged: Raised when the network connection is connected or disconnected.
Partial Friend Class MyApplication
Private Sub MyApplication_UnhandledException(sender As Object, e As Microsoft.VisualBasic.ApplicationServices.UnhandledExceptionEventArgs) Handles Me.UnhandledException
MessageBox.Show(e.Exception.ToString) '<-- the exception object
e.ExitApplication = True '<-- True if you want the application to close; false to continue - if it can
End Sub
End Class
End Namespace
Note that there are still some "über-exceptions" that can't be caught even with this method (out of memory for example), but this way you can log what exceptions are not being correctly handled on your code, or what scenarios are actualy happening that weren't considered at the beggining.
More info here
As a side note: don't rely too much on this event. This has to be for extremely exceptional cases, as what is caught here should be treated ASAP in the corresponding class/module/method. This is a helpful tool for debugging and test cases but having too much exceptions being handled by this event would be a sign of something wrong in your code.
It depends on what environment your application is running in. If you are using WPF or WinForms, you would launch your application using a main method instead of directly launching a form or page. Your main method should then wrap the calls to instantiate the UI in a try catch block.
So, for a WinForms Application, you could do something like this:
Sub Main
Try
Dim MainUI As New Form1
MainUI.Show()
Application.Run
Catch ex As Exception
'Do that fancy exception processing
End Try
End Sub
You can do something similar with WPF. But, WPF also supports an event model where you are notified of exceptions, very similar to the one that ASP.Net uses.
You will never be able to catch the StackOverflowException.
All the others for sure yes. I'm not familiar with VB, but it is easy to achieve in C#. For VB, I think the generic exeption handler could be
Try
...
Catch e As Exception
...
End Try
Of course this has to wrap all your code. You can find more for examples here.
If you have forms application, it is not practical to have the handler around Application.Run() as the only event handler. Keep it there, but add also two others:
When inside the form, and exception occurs, you want to keep execution in that context – keep the form open etc. For this purpose, use ThreadExceptionEventHandler.
And also, for case when application is irrepairably crashing on some problem, add handler to Dispatcher.UnhandledException event. It is last thing executed before the application ends. You can log the exception to disk for later investigation etc. Very useful.
Let's see some good resource how they are applied.

Difference between form.close and application.exit

I am recently out of school, working my first job as a programmer. We have a user-reported bug that is occurring when our application timeout timer closes the application. I'm pretty sure I've narrowed it down, but am curious as to why the original programmer would have done this, if it's good coding practice, and if so, I am curious if anyone has a way to handle this. We are getting a post-closing system error, as it occurs after the main form closes, so we don't get any exception log input.
The close functionality of the timeoutTimer_tick handler does the following:
For iCount As Int16 = Application.OpenForms.Count - 1 To 0 Step -1
Try
Application.OpenForms(iCount).Close()
Catch
End Try
Next
Try
Application.Exit()
Catch ex As Exception
End Try
The program is set with the application property to close when the main form closes (not ALL open forms are closed). This makes me wonder why we're looping through each form and closing them individually, and then calling Application.Exit()
I'm pretty sure our error is because of the Application.Exit call after all open forms are closed. It doesn't see the main form, as it was closed during the loop, and throws an error. I feel like we should be using one or the other, but not both.
Any input or advice? Which is better, or are either better (or should this code work without error, and I am simply wrong).
Thanks
In WinForms you have a Shutdown Mode setting (Project Properties>Application Tab)
This allows you to specify When startup form closes or When last form closes
So logically you should not need Application.Exit .If you do then there is some other object hanging around in memory that you need to dispose of (something started on a thread / background worker etc)
So If you have the startup form setting:
[StartupFormName].Close
or if you have the last form setting:
Do While My.Application.OpenForms.Count > 0
My.Application.OpenForms(0).Close()
Loop

Update UI form from worker thread

I am new to multi-threading in VB.NET and have come across a problem whereby I am wanting to append text to a text box on a form from a service thread running in the background.
The application I am developing is a client/server listener, I have been able to get the client and server PC's to talk with each other (confirmed through MsgBox), however I am now struggling to get the service thread on the server to append the text to the textbox, nothing vissible occurs.
I have a form named testDebug which calls a class (RemoteSupport), this class does all the handshake tasks and updates the textbox with the connection data.
Can anyone identify where I am going wrong and point me in the right direction?
The following is the code I have:
The form has a textbox named txtOutput, the following is from the remoteSupport class
Dim outMessage As String = (encoder.GetString(message, 0, bytesRead))
MsgBox(outMessage, MsgBoxStyle.Information, "MEssage Received")
If outMessage IsNot Nothing Then
If testDebug.InvokeRequired Then
' have the UI thread call this method for us
testDebug.Invoke(New UpdateUIDelegate(AddressOf HandleClientComm), New Object() {outMessage}) '
Else
testDebug.txtOutput.AppendText(outMessage)
End If
'RaiseEvent MessageReceived(outMessage) // a previous attempt to use custom events
End If
I am not sure if the invoke method is the ideal solution or if custom events are, I have spent some time on trying to get custom events to work, but these didnt work either.
// In the RemoteSupport class
Public Delegate Sub MessageReceivedHandler(ByVal message As String)
Public Shared Event MessageReceived As MessageReceivedHandler
// Located throughout the RemoteSupport class where debug information is required.
RaiseEvent MessageReceived(outMessage)
// Located in the code-behind of the form
Private Sub Message_Received(ByVal message As String)
testDebugOutput(message) // this is a function I have created
// to append the text to the text box
End Sub
The code supplied has been cut down so if there is anything else that you want to see or any questions please let me know.
Thanks for your assistance.
EDIT: I have uploaded the two VB files (form and class) to my site, I would appreciate it if someone could have a look at it to help me with identifying the problem with the UI not updating.
I have tried a few other things but nothing seems to be updating the UI once the worker thread has started.
Form: mulholland.it/testDebug.vb.txt
Class: mulholland.it/remoteSupport.vb.txt
Thanks for your assistance.
Matt
I have a form named testDebug...
If testDebug.InvokeRequired Then
This is a classic trap in VB.NET programming. Set a breakpoint on the If statement. Notice how it returns False, even though you know that the code is running on another thread?
InvokeRequired is an instance property of a Form. But testDebug is a class name, not a reference to an instance of a form of type testDebug. That this is possible in VB.NET has gotten a lot of VB.NET programmers in deep trouble. It is an anachronism carried over from VB6. It completely falls apart and blows up in your face when you do this in a thread. You'll get a new instance of the form, instead of the one that the user is looking at. One that isn't visible because its Show() was never called. And otherwise dead as a doornail since the thread isn't running a message loop.
I answered this question several times already, with the recommended fix. I'll just refer you to them rather than rehashing it here:
Form is not updating, after custom class event is fired
Accessing controls between forms
The Delegate method is likely the way you want to go, but I don't see the declaration of the UpdateUIDelegate anywhere
I believe your code should look something like this (assuming you have a reference to the testdebug form local to your remotesupport class
Dim outMessage As String = (encoder.GetString(message, 0, bytesRead))
MsgBox(outMessage, MsgBoxStyle.Information, "MEssage Received")
If outMessage IsNot Nothing Then
If testDebug.InvokeRequired Then
' have the UI thread call this method for us
testDebug.Invoke(New MessageReceivedHandler(AddressOf Testdebug.Message_Received), New Object() {outMessage})
Else
testDebug.txtOutput.AppendText(outMessage)
End If
end if