1

i'm part of a (small) development team who took over the development and support of a C#.Net program. It has ~ 300.000 LOC and the software design makes it impossible to change anything big without causing millions of side effects. It's like trying to turn a jungle full of poisenous snakes into a nice little garden. Using only a small scissor.

The application is a big WinForms-Application with Database access.

About the problem: A customer received our software and cannot run it. Unlike other customers, they have multiple Windows Server 2008 R1 terminal servers and installed the software on a network drive. Their users connect to one of the terminal servers and run our application (and others, like windows office etc) from the network drive. Our application however crashes after ~ 5 seconds without any notice. Our loading screen appears and closes again. The application produces a log file which shows this exception:

2014-08-04 11:15:23 [3372] ERROR – An exception occurred:
'OutOfMemoryException': Not enough memory. 
System.Drawing

at System.Drawing.Graphics.CheckErrorStatus(Int32 status)
at System.Drawing.Graphics.DrawImage(Image image, Rectangle destRect, Int32 srcX, Int32 srcY, Int32 srcWidth, Int32 srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttrs, DrawImageAbort callback, IntPtr callbackData)
at System.Drawing.Bitmap.MakeTransparent(Color transparentColor)
at System.Windows.Forms.ImageList.CreateBitmap(Original original, Boolean& ownsBitmap)
at System.Windows.Forms.ImageList.CreateHandle()
at System.Windows.Forms.ImageList.get_Handle()
at System.Windows.Forms.ImageList.GetBitmap(Int32 index)
at System.Windows.Forms.ImageList.ImageCollection.GetEnumerator()
at <our application>.InitializeHtmlResources()

The method that calls ImageCollection.GetEnumerator() is this:

void InitializeHtmlResources()
    {
        string baseDirPath = ... //It's in AppData/Local/<ourFolder>/Icons;


        int index = -1;
        foreach (Image image in UIResources.Icons.Images)
        {
            index += 1;

            image.Save(baseDirPath + Path.DirectorySeparatorChar + index.ToString(), ImageFormat.Png);
        }
    }

Those images are stored inside a Resource.Icons.dll. The icons have their own project in the solution that contains of a few helper classes (like UIResources) and contains a folder with every icon we use + an xml where they are all listed, named and indexed. UIResources is a static class that allows access to the icons and the image list. This is how the property "Images" is initialized:

...
// Code snippet of Images Initialization
var ilist = new ImageList();
ilist.ColorDepth = ColorDepth.Depth32Bit;
ilist.ImageSize = new Size(16, 16);
ilist.TransparentColor = Color.Fuchsia;
UIResources.Icons.Images = ilist;
...

This method is used to extract an Icon from the DLL file.

static IEnumerable<IconInfo> GetIcons()
    {
        XDocument doc;
        using (var stream = GetResourceStream("Our.Namespace.Resources.Icons.Icons.xml"))
        {
            doc = XDocument.Load(stream);
        }

        // ReSharper disable once PossibleNullReferenceException            
        foreach (var elem in doc.Element("Icons").Elements("Icon"))
        {
            int index = (int)elem.Attribute("Index");
            var bmp = ReadIcon("Icons", (string)elem.Attribute("FileName"));
            string name = (string)elem.Attribute("Name");

            yield return new IconInfo(bmp, index, name);
        }

    }
    static Bitmap ReadIcon(string kind, string fileName)
    {
        using (var stream = GetResourceStream("Our.Namespace.Resources." + kind + "." + fileName))
        {
            return new Bitmap(stream);
        }
    }

    static Stream GetResourceStream(string resourceName)
    {
        return typeof(IconProvider).Assembly.GetManifestResourceStream(resourceName);
    }

IconInfo is only a record containing the values.

And finally, the ImageList is filled with values:

foreach (var icon in GetIcons())
            UIResources.Icons.Images.Add(icon.Name, icon.Image);

The application works fine. But when that customer runs the software on his terminal server via Remote Desktop, the application crashes and throws an OutOfMemory Exception in InitializeHtmlResources() right at the foreach-loop (when accessing the enumerator of ImageList).

The confusing part: A "OutOfMemory" exception is thrown, though the memory is neither full, nor is the 2 GB limit for 32bit application reached. The app peaks at 120 MB during loading.

I have absolutely no idea how this error is caused and spent the last 2-3 days trying to find a solution. I haven't.

I appreciate every bit of advice you can give me.

EDIT:

I tried disabling the InitializeHtmlResources-Method. This enabled the application to start. However: After working a few seconds with the application, an outofmemory exception appeared anyway. The cause is another ImageList accessor.

It works fine with Server 2012. We created a VM with Windows Server 2008 and the error happens there too.

Alexander Mills
  • 277
  • 3
  • 18
  • Only a comment but I am wondering if the stream is not getting disposed because you are exiting the using block. – paparazzo Aug 07 '14 at 13:16
  • I always thought the using clause will make sure it is disposed in any case including exceptions etc.. Do you know how many icons get loaded before it crashes? Can you log out maybe the Icon.Name? How many icons are there in total? – TaW Aug 07 '14 at 14:25
  • I just tested it: nope :\ thats not it. The whole loading of the images works fine. the whole thing only crashes once the foreach loop starts – Alexander Mills Aug 07 '14 at 14:52
  • @TaW Read my comment. The return is IN the using block so it never really finishes the using block. – paparazzo Aug 07 '14 at 15:34
  • 1
    @Blam: Here is the order of events' straight form the man: _A using statement is just syntactic sugar for a try/finally block, and as Grzenio says it's fine to return from a try block too. The return expression will be evaluated, then the finally block will be executed, then the method will return._ [Jon Skeet's answer](http://stackoverflow.com/questions/662773/returning-in-the-middle-of-a-using-block) – TaW Aug 07 '14 at 16:03
  • _crashes once the foreach loop starts_ That's this one: `foreach (Image image in UIResources.Icons.Images)` with index being 0? Can you try to run a tiny test app to see if the machine(s) can actually handle those image formats? always an uncomfortable situation with a customer; or can you reproduce it an a machine with a similar setup? – TaW Aug 07 '14 at 16:09
  • @TaW That is why it was a comment and wondering. Thanks – paparazzo Aug 07 '14 at 16:25
  • We set up a simple VM with Windows Server 2008, installed .NET 4.0 and ran our application. Same problem. @TaW we just created a simple application that basically only creates the image list, extracting the images from the dll, and storing them in AppData/Local/... . This application works. I also tried to disable the whole image part. The application then opens, but shows the exception anyway after working some time. I guess the error is not the image list, but something more complex about the system :\ (Application works fine on Windows Server 2012) – Alexander Mills Aug 08 '14 at 07:22

2 Answers2

1

We found the issue! Instead of

UIResources.Icons.Images.Add(icon.Name, icon.Image);

to fill the ImageList, we now use

UIResources.Icons.Images.Add(icon.Name, new Bitmap(icon.Image));

Now our application works on Windows Server 2008 :-)

Alexander Mills
  • 277
  • 3
  • 18
0

Is it possible?

Memory limitations of 32 bit apps on 64 bit Terminal Server 2008 Standard