13

My program is a CRM, I used Rad Ribbon Bar, so Many Buttons with images, RadGridView (which some columns contain images) and so many other controls which contain images. It's a mdi parent/child program.

Just an Example of RibbonBar

In so many cases while loading a mdi child or working with some grid views the program will hang and give me this error:

OutOfMemoryException occurred in System.Drawing.dll

I tried GC.Collect() on certain parts but no success. For setting images there is no code! for example for setting an image for a button I used its properties in visual studio. I have set All other control images in this way using the properties panel in visual mode.

enter image description here

and These are some designer codes related to drawing:

    btnCustomerList.Image = global::MyApp.Properties.Resources.CustomerList32;

    gridViewCommandColumn1.Image = global::MyApp.Properties.Resources.ViewShop32;

and When The error comes after a while working with the app, it will appear in Program.cs and in the line Application.Run(new MainForm());:

    static void Main()
    {
        AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", AppDomain.CurrentDomain.BaseDirectory + "\\Settings.config");
        bool ok;
        Mutex m = new Mutex(true, WindowsIdentity.GetCurrent().Name.ToString().Split('\\')[1] + "MyApp", out  ok);
        if (ok)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            // The Error will cause HERE
            Application.Run(new MainForm());

            GC.KeepAlive(m);
        }
        else
            Application.Exit();
    }

MainForm is the mdi parent which contains Ribbon Bar. and this is the Full stack trace:

at System.Drawing.Image.FromHbitmap(IntPtr hbitmap, IntPtr hpalette)
at System.Drawing.Image.FromHbitmap(IntPtr hbitmap)
at System.Drawing.Icon.ToBitmap()
at System.Windows.Forms.ThreadExceptionDialog..ctor(Exception t)
at System.Windows.Forms.Application.ThreadContext.OnThreadException(Exception t)
at System.Windows.Forms.Control.WndProcException(Exception e)
at System.Windows.Forms.Control.ControlNativeWindow.OnThreadException(Exception e)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
at MyApp.Program.Main() in d:\\MyApp\\Application\\MyApp\\Program.cs:line 36"

UPADTED:

the code for calling mdi-children by clicking on ribbon bar buttons is here:

private void btnCustomerList_Click(object sender, EventArgs e)
{
    OpenForm(new FormCustomerList(), "Customer List");
}

private void btnCustomerRelated_Click(object sender, EventArgs e)
{
    OpenForm(new FormCustomerRelated(), "Customer Related");
}

and Here is OpenForm method:

private void OpenForm(Form formType, string Caption)
{
    foreach (Form nform in Application.OpenForms)
    {
        if (nform.GetType() == formType.GetType())
        {
            nform.Activate();
            return;
        }
    }
    this.MdiChildren.OfType<Form>().ToList().ForEach(x => x.Dispose());
    GC.Collect();

    Form form = formType;
    form.MdiParent = this;
    form.Dock = DockStyle.Fill;
    form.Show();
    this.Text = Caption;
}

and in every mdi child's form constructor, after InitializeComponent(); I wrote GC.Collect(); also. But as told in comments, the GDI objects in task manager will increase and increase till 10000 objects and then application will crash.

UPADTED: THE MOST ISSUE

It seems I have found the part which cause the most GDI objects. In every form there are some controls like textboxes, drop down list etc. I have set some rules for them, for example if user enter a textbox, its back color should be yellow and after leave it should be white again. So there is a main method which I call in form load to literate through all controls and find the target ones and add for example enter and leave events with the defined rules. something Like This:

private void FormCustomerList_Load(object sender, EventArgs e)
{
    ClassCRMControls.AddEventHandler(this);
}  

and inside ClassCRMControls class:

public static void AddEventHandler(Control parent)
{
    foreach (Control c in parent.Controls)
    {
        if (c.GetType() == typeof(RadTextBox))
        {
            c.Enter += new EventHandler(ClassCRMControls.EnterEvent);
            c.Leave += new EventHandler(ClassCRMControls.LeaveEvent);
        }
        else
            AddEventHandler(c);
    }
}

private static void EnterEvent(object sender, EventArgs e)
{
    (sender as RadTextBox).TextBoxElement.TextBoxItem.BackColor = Color.FromArgb(255, 251, 147);
}

private static void LeaveEvent(object sender, EventArgs e)
{
      (sender as RadTextBox).TextBoxElement.TextBoxItem.ResetValue(LightVisualElement.BackColorProperty, ValueResetFlags.Local);
}
Inside Man
  • 4,194
  • 12
  • 59
  • 119
  • Log full stack trace of this exception, maybe it will provide some clue. – Evk Nov 02 '17 at 07:11
  • 2
    Hmya, the program has a bad handle leak. It is so bad that even the exception dialog cannot be shown anymore. It is something you can see in Task Manager, Processes tab. Add the column for GDI Objects, you'll see it steadily increase and the show is over when it reaches 10000. Nobody wins any prizes for not disposing these images, but it shouldn't be fatal. You already said that you tried GC.Collect(), it that doesn't help then the finalizer thread is probably deadlocked. Enable unmanaged debugging and find out what it is doing. – Hans Passant Nov 02 '17 at 07:40
  • 1
    Also look at the USER Objects column, leaking those is another traditional bug in Winforms caused by using Controls.Clear() or Remove(). – Hans Passant Nov 02 '17 at 07:41
  • 1
    Sometimes it happens that some objects remain erroneously referenced in some part of the code so that the GC can't collect them. You could use the CLR Profiler, downloadable from Microsoft website, to see the allocation profile of your managed application. – Simone Cifani Nov 02 '17 at 08:08
  • Excuse me friends, I was away for a week for `Arbaein`. Just Updated my question – Inside Man Nov 12 '17 at 06:14
  • You need to limit the amount of GDI objects simultaneously alive in your app. You should either load/release images, or, which I'd vote for, create a single source of images using ImageList, for example, and reuse images from there. Btw, 10k is the per process handle limit, and itay be changed by registry setting, yet I would not recommend going down this road. – Alex Seleznyov Nov 12 '17 at 10:00

2 Answers2

5

I have found the source of the problem and it was the custom animated cursor I used for grids and other controls too. I initialize it like this:

this.Cursor = ClassObjects.CreateAnimatedCursor("C:\\aniCur.ani"));

Since I loaded this cursor from the file every time I used it in any way, more and more GDI Objects got created.

So I declared a public static cursor the main() of the respective form like this:

public static Cursor animCur = ClassObjects.CreateAnimatedCursor("C:\\aniCur.ani"));

and then whenever I need to use this cursor I just reference this object public static cursor from the form.

this.Cursor = MainForm.animCur;

That's It :)

How did I find it? I just try to remove (commenting) some codes which I suspected them, then I checked GDI objects in task manager. After some testing it became apparent that the endless loading of new cursor objects was causing the problem.

Inside Man
  • 4,194
  • 12
  • 59
  • 119
  • 3
    This is not a very useful answer. Surely future readers of this Q+A will not have the exact same problem and would want to know how you discovered the cause of this handle leak. And consider to just do this correctly so that the GC *can* correctly avoid the leak. Sample code [is here](https://stackoverflow.com/a/4306984/17034), the hack with the "ownHandle" field takes care of it. – Hans Passant Nov 12 '17 at 17:23
  • 1
    For future readers OP edited answer to include his GDI Object count analysis. Hans discussed it [here](https://stackoverflow.com/a/8306253/495455), observing the GDI Count while you use the application. There is more info and steps on how to do this [here](https://stackoverflow.com/a/40232021/495455) – Jeremy Thompson Nov 13 '17 at 03:59
  • 1
    I disagree, this is a useful answer because it tells future readers that "OutOfMemoryException" has more sources for errors than *just* out of memory conditions. Here there was a leakage of GDI handles, and if this eventually causes the same exception then it is a useful situation to have described to you. However, there are other Q&A posts here on SO that details these things in greater detail. Though this doesn't invalidate this question and its answer, perhaps it might be worthwhile to close it as a duplicate? – Lasse V. Karlsen Nov 17 '17 at 12:56
5

There can be multiple reasons for OutOfMemoryExceptions. I have discussed 6 of them in another question.

In this case, after the comments and the edit, it became clear that GDI issues occur as well. You can detect these issues by showing an additional column in task manager:

GDI objects shown in Task Manager

GDIView is a far better application for GDI Leak analysis, because it also tells you the type of the GDI handle that got lost. It also has absolute and relative counters, so you can see how many of them get lost during a particular action.

GDIView details

The number of GDI handles can be configured in Registry. Do not use that as a permanent solution. Instead, with the additional info from GDIView, find the piece of code that leaks the GDI object.

When you run into the limit of GDI handles, the application typically starts looking bad: things are not painted any more and you get black rectangles in some places. However, this behavior is not necessary. In OP's case, black rectangles were not part of the description.

Thomas Weller
  • 55,411
  • 20
  • 125
  • 222