We have an extension running in TIBCO Spotfire with which it has a pretty complex interraction. It's a C# Winform application. I want to be able to catch all uncaught exceptions rather than letting them crash the app. One example happens in one of the event handlers we wrote. When I make Visual Studio catch the exception, the call stack below our event handler is:
C1.Win.C1Command.4.dll!C1.Win.C1Command.C1Command.OnClick(C1.Win.C1Command.ClickEventArgs e) Unknown
C1.Win.C1Command.4.dll!C1.Win.C1Command.C1Command.Invoke(C1.Win.C1Command.ClickEventArgs e) Unknown
C1.Win.C1Command.4.dll!C1.Win.C1Command.BarAddIn.InvokeCommandLink(C1.Win.C1Command.C1CommandLink cl, C1.Win.C1Command.BarAddIn.InvokedBy invokedBy) Unknown
C1.Win.C1Command.4.dll!C1.Win.C1Command.ToolBarAddIn.OnMouseUp(System.Windows.Forms.MouseEventArgs e) Unknown
C1.Win.C1Command.4.dll!C1.Win.C1Command.C1ToolBar.OnMouseUp(System.Windows.Forms.MouseEventArgs e) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.WmMouseUp(ref System.Windows.Forms.Message m, System.Windows.Forms.MouseButtons button, int clicks) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) Unknown
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) Unknown
[Native to Managed Transition]
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason, int pvLoopData) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason, System.Windows.Forms.ApplicationContext context) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) Unknown
Spotfire.Dxp.Forms.dll!Spotfire.Dxp.Forms.MainFormManager.Run() Unknown
Spotfire.Dxp.Main.dll!Spotfire.Dxp.Main.Program.StartMain.AnonymousMethod__0() Unknown
Spotfire.Dxp.Framework.dll!Spotfire.Dxp.Framework.ApplicationModel.ApplicationThread.AssociateToCurrentThread(Spotfire.Dxp.Framework.DocumentModel.Executor executor) Unknown
Spotfire.Dxp.Loader.dll!Spotfire.Dxp.Loader.ProgramLoader.StartMain(Spotfire.Dxp.Framework.ApplicationModel.ConnectivityService connectivityService, string startupFile, System.IDisposable startupFileLock, string qualificationExportPath, string applicationProfilerConfigPath, string startupUri, Spotfire.Dxp.Loader.SplashWrapper splashWrapper, Spotfire.Dxp.Framework.ApplicationModel.ModulesService modulesService, Spotfire.Dxp.Framework.ApplicationModel.LocalizationService localizationService, Spotfire.Dxp.Framework.AddIn.Registry.AddInRegistry addIns, Spotfire.Dxp.Framework.ApplicationModel.CobrandingService cobrandingService, bool disableMultipleViews, string pathToDxpExeWithConfig) Unknown
Spotfire.Dxp.Loader.dll!Spotfire.Dxp.Loader.ProgramLoader.PrepareForStartingMain(System.Collections.Generic.Dictionary<string, object> state, string[] args, string upgradePath, System.Collections.Generic.List<string> moduleFolders, Spotfire.Dxp.Loader.StarterSplash starterSplash, bool isCalledByLegacyStarter, bool isLog4netConfigured) Unknown
Spotfire.Dxp.Loader.dll!Spotfire.Dxp.Loader.ProgramLoader.InternalExecute(System.Collections.Generic.Dictionary<string, object> state, string[] args, object splash, System.Version starterVersion, System.Collections.Generic.IList<string> moduleFolderList, string upgradePath, bool isCalledByLegacyStarter) Unknown
Spotfire.Dxp.Loader.dll!Spotfire.Dxp.Loader.ProgramLoader.Execute(System.Collections.Generic.Dictionary<string, object> state, string[] args, object splash, System.Version starterVersion, System.Collections.Generic.IList<string> moduleFolderList, string upgradePath) Unknown
[Native to Managed Transition]
[Managed to Native Transition]
Spotfire.Dxp.exe!Starter.Program.ProgramLoaderWrapper.Execute(string fullLoaderAssemblyName, System.Collections.Generic.Dictionary<string, object> loaderState, string[] args, object splash, System.Version starterVersion, System.Collections.Generic.IList<string> moduleFolderList, string upgradePath) Unknown
[AppDomain (Spotfire.Dxp.exe, #1) -> AppDomain (Spotfire.ApplicationHost, #2)]
Spotfire.Dxp.exe!Starter.Program.Run(Spotfire.Dxp.Starter.StarterSplashWrapper splash, System.Collections.Generic.Dictionary<string, object> loaderState, bool serverless) Unknown
Spotfire.Dxp.exe!Starter.Program.ExecuteRunLoopWithBootstrapFlag(string[] cmdArgs, bool calledByBootstrapper) Unknown
Spotfire.Dxp.exe!Starter.Program.Main(string[] cmdArgs) Unknown
As you can see we don't own the Main, Spotfire does.
As far as I can tell the earliest point of entry is OnApplicationInstanceCreated.
Normally in a Winform application you'd need to do these 2:
AppDomain.CurrentDomain.UnhandledException += ...
Application.ThreadException += ...
See https://stackoverflow.com/a/5762806/788301 . You also need to call
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
but that one crashes with "Thread exception mode cannot be changed once any Controls are created on the thread."
meaning we are too late to set it.
There is not a GetUnhandledExceptionMode so I don't know what it is, but I suspect it is set to not catch them, otherwise I'd expect
Application_ThreadException
to catch our exception.
From what I remember there may be a few other things that throw exceptions outside of these 2 paths - has to do with thread pools and/or timers and/or dispatchers,
but I think in this case it's that we can't SetUnhandledExceptionMode is the reason it's not caught.
The fact that there are a few switches between native and managed might also be a factor.
protected override void OnApplicationInstanceCreated(AnalysisApplication application)
{
...
System.Windows.Forms.Application.ThreadException += Application_ThreadException; // Main thread exceptions. Currently not caught probably because of UnhandledExceptionMode
try
{
// crashes with "Thread exception mode cannot be changed once any Controls are created on the thread."
System.Windows.Forms.Application.SetUnhandledExceptionMode(System.Windows.Forms.UnhandledExceptionMode.CatchException);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; // unhandled exceptions on other threads
...
}
My hope was that Spotfire has someting like UnhandledException event, but I do not see any. Of course I can put a try catch around every method we wrote, but at this point there is so mauch code that it's not feasible.