I have a .net C# application which uses some Chart and PictureBox controls which (albeit quite rarely) spontaneously closes. My global exception handler isn't triggered - the app just vanishes.
The following is from the Application event log (three separate events):
Application: MyApp.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.AccessViolationException
at System.Drawing.SafeNativeMethods+Gdip.GdipDrawImageRectI(System.Runtime.InteropServices.HandleRef, System.Runtime.InteropServices.HandleRef, Int32, Int32, Int32, Int32)
at System.Drawing.Graphics.DrawImage(System.Drawing.Image, Int32, Int32, Int32, Int32)
at System.Drawing.Graphics.DrawImage(System.Drawing.Image, System.Drawing.Rectangle)
at System.Windows.Forms.PictureBox.OnPaint(System.Windows.Forms.PaintEventArgs)
at System.Windows.Forms.Control.PaintWithErrorHandling(System.Windows.Forms.PaintEventArgs, Int16)
at System.Windows.Forms.Control.WmPaint(System.Windows.Forms.Message ByRef)
at System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
at System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
Faulting application name: MyApp.exe, version: 14.9.8764.31123, time stamp: 0x659050b3
Faulting module name: gdiplus.dll, version: 10.0.19041.3636, time stamp: 0xb8405853
Exception code: 0xc0000005
Fault offset: 0x0000000000024f80
Faulting process id: 0x2cd4
Faulting application start time: 0x01da3d633ffe83fc
Faulting application path: C:\MyApp\MyApp.exe
Faulting module path: C:\WINDOWS\WinSxS\amd64_microsoft.windows.gdiplus_6595b64144ccf1df_1.1.19041.3636_none_91a19322cc8a92a3\gdiplus.dll
Report Id: b44802f6-cb81-4188-89a2-6236e1ff5a37
Faulting package full name:
Faulting package-relative application ID:
Faulting application name: MyApp.exe, version: 14.9.8764.31123, time stamp: 0x659050b3
Faulting module name: gdiplus.dll, version: 10.0.19041.3636, time stamp: 0xb8405853
Exception code: 0xc000041d
Fault offset: 0x0000000000024f80
Faulting process id: 0x2cd4
Faulting application start time: 0x01da3d633ffe83fc
Faulting application path: C:\MyApp\MyApp.exe
Faulting module path: C:\WINDOWS\WinSxS\amd64_microsoft.windows.gdiplus_6595b64144ccf1df_1.1.19041.3636_none_91a19322cc8a92a3\gdiplus.dll
Report Id: c5b44473-b9e4-4555-a8cf-42f32959cbb5
Faulting package full name:
Faulting package-relative application ID:
It seems to be faulting outside my code. When trying to trace the issue, I came across this:
... but it seems to me that is talking about initialising the GDI+ engine if you're running it from within a dll (which I'm not - this is an exe). Plus, it only crashes rarely (and all the charts etc work fine otherwise), but I imagine if I were failing to initialise GDI+ properly it wouldn't work at all.
Should I be initialising GDI+ in my Form_Load anyway? ... or is there some other reason why the app is throwing an exception and exiting please?
Any help gratefully received.
EDIT: to include some further information. I'm using the WordCloud library by Brian Cullen on Nuget, which is called as follows:
var wc = new WordCloudGen(new_width, new_height);
Image wordcloud_generated_image = wc.Draw(database_wordcloud_words, database_wordcloud_frequencies);
database_results_table.Invoke((MethodInvoker)delegate
{
wordcloud_image.Image = wordcloud_generated_image;
...
It's multi-threaded (ie. the wordcloud is generated in the background, and I don't want it to interfere with the user's ability to interact elsewhere on the form). I think for present purposes the detail can be sidestepped. If, for the sake of the argument, we assume that the WordCloud library can in some circumstances return a malformed image, how can I stop the error in GDI+. The whole block of code is wrapped in a try/catch (and there's a global exception handler too), but it's not trapping this one.
EDIT 2:
Having implemented a custom PictureBox class and enabling corrupted state exception handling, I'm able to trap the error:
UnhandledExceptionEventArgs
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
at System.Drawing.SafeNativeMethods.Gdip.GdipDrawImageRectI(HandleRef graphics, HandleRef image, Int32 x, Int32 y, Int32 width, Int32 height)
at System.Drawing.Graphics.DrawImage(Image image, Int32 x, Int32 y, Int32 width, Int32 height)
at System.Drawing.Graphics.DrawImage(Image image, Rectangle rect)
at System.Windows.Forms.PictureBox.OnPaint(PaintEventArgs pe)
at Timesheet_Writer.ErrorManagedPictureBox.OnPaint(PaintEventArgs e) in D:\Develop\Visual C# 2017\MyApp.cs:line 39
at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer)
at System.Windows.Forms.Control.WmPaint(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
It seems to do it after the app has been minimised and left for a while, and then the user restores the window. I do have a feeling it's something to do with the image being garbage collected, notwithstanding it's actually still in use.
I read this with interest: https://www.codeproject.com/Tips/246372/Premature-NET-garbage-collection-or-Dude-wheres-my
I'll see if explicitly managing garbage collection via KeepAlive can be made to work.
I've found the answer to this.
I don't think that the garbage collection is actually destroying the object; rather, it can move objects more than 85KB in memory to optimise them (see e.g. https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap). If this happens between managed and unmanaged code, the unmanaged code isn't aware it's moved, has the old pointer, and tries to read from memory space which is actually no longer associated with the Image in the PictureBox - hence the access violation.
I created my own PictureBox class and added code to "pin" the Image object in the garbage collector so it doesn't get moved. Since I did that, I haven't been getting the exception.
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace MyApp
{
public partial class MemoryManagedPictureBox : System.Windows.Forms.PictureBox
{
public volatile Boolean exception_has_occurred = false;
GCHandle memory_lock;
~MemoryManagedPictureBox()
{
if (this.Image != null)
{
this.Image.Dispose();
memory_lock.Free();
}
}
public void set_image(ref Image im)
{
if ( this.Image != null )
{
this.Image.Dispose();
memory_lock.Free();
}
this.Image = im;
memory_lock = GCHandle.Alloc(this.Image, GCHandleType.Normal);
}
public void clear_image()
{
if (this.Image != null)
{
this.Image.Dispose();
this.Image = null;
memory_lock.Free();
}
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
try
{
base.OnPaint(e);
exception_has_occurred = false;
}
catch
{
exception_has_occurred = true;
}
}
}
}