Click here to Skip to main content
15,392,813 members
Please Sign up or sign in to vote.
4.00/5 (1 vote)
See more:
Im having an issue with a piece of code that i execute every 500ms to draw a custom icon on the tray based on the CPU utilization (as task manager does for example)

The code works just fine except that after a short period of ran it throws a generic GDI error that i have no idea how to resolve.

Does anyone knows how to handle this situation?

Dim bCPU As New Bitmap(16, 16)
Dim gCPU As Graphics = Graphics.FromImage(bCPU)
'Draw Grid
For i As Integer = 1 To 16 Step 2
  gCPU.DrawLine(New Pen(Color.Green, 1), i, 0, i, 16)
  gCPU.DrawLine(New Pen(Color.Green, 1), 0, i, 16, i)
'Draw Value
Dim lines As Integer = CInt(Math.Round((16 / 100) * perf_MeterCPU.LastValue, 0))
For i As Integer = 0 To lines Step 1
  gCPU.DrawLine(New Pen(Color.Lime, 1), 0, 16 - i, 16, 16 - i)
'Draw Border
ControlPaint.DrawBorder3D(gCPU, 0, 0, 16, 16, Border3DStyle.SunkenInner)
TrayIconCPU.Icon = Icon.FromHandle(m_TrayBitmap.GetHicon)

The exception being thrown is:

System.Runtime.InteropServices.ExternalException (0x80004005): A generic error occurred in GDI+.
CPallini 28-Mar-11 15:35pm
Why do you dispose bCPU before gCPU?
creizlein 28-Mar-11 15:59pm
i don't know... hehe, good question, but it made no difference so far..

The first thing that springs to mind is that you are using the wrong numbers: you have decalared a bitmap that is 16 pixels by 16 pixels, but you are drawing outside the bitmap area: pixels 0-16 inclusive.

This shouldn't cause a problem though, the extras should be outside the clipping region and not be drawn.

Have you tried removing all the code between the declaration of gCPU and the disposal of bCPU to see if it still does it? If it doesn't, add some back until it does...
creizlein 28-Mar-11 15:26pm
I didn't tried, but just thinking it does not make sense, since the code does work perfect for a few minutes, regardless, i will try it out right away.
OriginalGriff 28-Mar-11 15:35pm
I know what you mean, but at the moment you are working blind: at least this way your should be able to narrow it down a bit...
One possibility is that Pen implements IDisposable, so really you shoudl either construct a single class level Pen object in Green, and one in Lime, or Dispose each of your new Pens. Thinking about it, there is a good chance that this would clear the problem: if you are executing this code at all frequently, you could easily run out of graphics handles for the Pens, which should cause a GDI+ exception.
creizlein 28-Mar-11 17:31pm
I updated the code and did what you suggested, i basically created global declared pens that never get disposed and i did the same for the bitmap, but the problem still persist, after a while of running the code the same error throws up
You're creating new Pens on every iteration of a loop, but never disposing them. This is a serious resource leak. You should be creating a SINGLE Pen object for each color you want to use, use those throughout your drawing routine, then Dispose them.

Better yet, on the startup of your app, you can get away with creating the Pens globally, then disposing of them when your app exists.
creizlein 28-Mar-11 21:04pm
Thanks for your reply, but i already did that, if you check the last comment i added to OriginalGriff response, i created a single globally declared pen and used it, but the same problem still throws.
Dave Kreskowiak 28-Mar-11 22:01pm
I was going entirely on the code snippet you posted. If the code you're actually using is different than that, you'll have to post that code too. I'll almost guarantee you've got a resource leak in your code very similar to the Pen problem I described.
I realize this is old, but I figured I'd point out that the real reason you are getting this error is because InPtrs are unmanaged, which means you need to dispose of them yourself as .NET will not do this for you.
This line:
TrayIconCPU.Icon = Icon.FromHandle(m_TrayBitmap.GetHicon)

needs to be changed to:
IntPtr Hicon = img.GetHicon();
Icon newIcon = Icon.FromHandle(Hicon);
TrayIconCPU.Icon = newIcon;

In order to call the DestroyIcon method you'll need this in your class:
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = CharSet.Auto)]
extern static bool DestroyIcon(IntPtr handle);

And you'll need to add using for System.Runtime.InteropServices:
using System.Runtime.InteropServices;

Just wanted to help out others that may come across this in the future.
SimonRees 24-Feb-14 14:39pm
Thankyou for taking the trouble to answer an old post. I have exactly the same line of code but in c#. I'll try your soloution and report back. I am a realtive newcomer to C# 2 years as opposed to 22 plain C. Micorsoft help is inpenitrable, you have to know how c# works before you can find out how it works. I thought .NET solved all these problems how on earth are you supposed to know that not everything is managed? How on earth are you supposed to do a dll import? this stuff just doesn't come up in the help searches not even in the help for Icon.FromHandle. How was I supposed to know I was using win32 stuff I thought I was using .NET.
I didn't eevn know I was using an IntPtr and I don't know waht one is.
BillyMac 12-Mar-14 23:11pm
This was a great help, thanks! After using DestroyIcon I could tell immediately it was working by watching a steady count of GDI Objects in task manager.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900