11

I have some code that is working perfectly on several machines (development, QA, UAT). Unfortunately, on production I'm getting "A Generic Error occurred in GDI+" on the bmp.Save(ms, ImageFormat.Png); As a result, I'm assuming you won't be able to reproduce the problem, but maybe someone can spot my error.

A few notes, I've searched around a lot for common solutions, note that this is saving to a MemoryStream so the file permission issues most people suggest do not apply, nor does the "bmp is locked while open" solution because again, I'm writing somewhere else. Finally, it's not because png requires a seekable stream because MemoryStream is seekable.

Note, if I change it to ImageFormat.Jpeg it works fine. I'm only having a problem with PNGs. I found mention of the registry key HKEY_CLASSES_ROOT\CLSID\{FAE3D380-FEA4-4623-8C75-C6B61110B681} potentially being the problem due to permissions. As a result, I set the key to allow Everyone to have read access to this key, no change.

public static MemoryStream GenerateImage(string text)
{
    MemoryStream ms = new MemoryStream();
    using (Bitmap bmp = new Bitmap(400,400))
    {
        bmp.Save(ms, ImageFormat.Png);
        ms.Position = 0;
    }
    return ms;
}

Here is the full stack trace:

[ExternalException (0x80004005): A generic error occurred in GDI+.]
System.Drawing.Image.Save(Stream stream, ImageCodecInfo encoder, EncoderParameters encoderParams) +616457
WP.Tools.Img.GenerateImage(String text) +383

Note: my question already enumerates the solutions in the proposed duplicate. None are the issue. If they were it would fail for JPEG as well.

dmeglio
  • 2,830
  • 1
  • 19
  • 24
  • 2
    Possible duplicate of [GDI+ exception saving a Bitmap to a MemoryStream](http://stackoverflow.com/questions/521927/gdi-exception-saving-a-bitmap-to-a-memorystream) – Thomas Weller Dec 06 '15 at 18:29
  • 1
    Maybe related: https://stackoverflow.com/questions/1053052/a-generic-error-occurred-in-gdi-jpeg-image-to-memorystream?rq=1 – Thomas Weller Dec 06 '15 at 18:30
  • 1
    @Thomas Weller I don't believe that is the same problem. First I'm not freeing the memory anywhere, second if that were the problem I'd expect jpeg to fail as well, not just png. – dmeglio Dec 06 '15 at 18:32
  • 1
    What happens when you do not load bg.png but instead create a fresh bitmap in memory? That way we can narrow the problem down. – usr Dec 06 '15 at 18:35
  • @usr great question, I just replaced it with `using (Bitmap cardBmp = new Bitmap(400,400))` and I still receive the same error. – dmeglio Dec 06 '15 at 18:37
  • Can you post a full stack trace? – Chris Haas Dec 06 '15 at 18:38
  • @ChrisHaas I added the stack trace. As you can see, it's not exactly helpful... – dmeglio Dec 06 '15 at 18:40
  • Delete all painting code. Update the code in the question. Narrow it down. – usr Dec 06 '15 at 18:50
  • @usr I just changed the code so it's basically 'create bmp, save it to MemoryStream' and the error still happens on the particular server. See the revised code in the question – dmeglio Dec 06 '15 at 18:53
  • If that code still fails then the machine is hosed. Two approaches: 1) Reinstall .NET if possible or upgrade to a later version such as 4.6.1. 2) Use Procmon to monitor failed file and registry accesses. Put the failing code into an infinite loop so that it is easier to spot in the trace. – usr Dec 06 '15 at 19:00
  • Q: Is there any reason the method must be `public static`? Is there any chance another thread could be making the same call concurrently? Q: Just for grins, what happens if you remove the "static"? – paulsm4 Dec 06 '15 at 19:01
  • @usr This is a small piece of code in a massive enterprise application, switching to 4.6.1 isn't an option unfortunately. I also highly doubt it's a .NET issue as the error is being thrown in GDI+ which is not part of .NET. – dmeglio Dec 06 '15 at 19:02
  • @paulsm4 I'm using it in a static context. However for the purpose of testing I made it non-static, no difference. – dmeglio Dec 06 '15 at 19:03
  • I suggested the upgrade because that automatically reinstalls .NET. Any way to do that would help. Maybe installing a .NET version also upgrades/reinstalls GDI+ who knows. What are you going to do now? – usr Dec 06 '15 at 19:06
  • @usr the machine already has 4.6.1 installed, this is just not using it. – dmeglio Dec 06 '15 at 19:07
  • 1
    One additional link: [Common Problems with rendering Bitmaps into ASP.NET OutputStream](http://weblog.west-wind.com/posts/2006/Oct/19/Common-Problems-with-rendering-Bitmaps-into-ASPNET-OutputStream). Note the part about `Remember PNG Images are ‘special’ `. – paulsm4 Dec 06 '15 at 19:11
  • 1
    Have you noticed the **Caution** at the end of [System.Drawing Namespace](https://msdn.microsoft.com/en-us/library/system.drawing.aspx): `Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions. ` – Ivan Stoev Dec 06 '15 at 19:11
  • @paulsm4 thanks, but as you'll notice, I'm not rendering it to `OutputStream` but rather a `MemoryStream` which is the proposed solution to that problem. – dmeglio Dec 06 '15 at 19:12
  • 1
    @IvanStoev Indeed I have. However I can reproduce the problem in a console app as well unfortunately. – dmeglio Dec 06 '15 at 19:13
  • Hmm, interesting. So you can load `png`, but cannot save it, even to a memory stream. – Ivan Stoev Dec 06 '15 at 19:15
  • @IvanStoev exactly. It's super strange. I've tried saving as bmp, jpeg, that's all fine. Only png seems to result in an error. – dmeglio Dec 06 '15 at 19:17
  • Why `ms.Position = 0;` ? – Mike Nakis Dec 06 '15 at 19:25
  • 1
    @MikeNakis just to make sure I'm at the beginning of the stream before I use it. However it doesn't even get to that line of code. – dmeglio Dec 06 '15 at 19:27
  • 1
    Would you mind adding the .NET versions (compiled and installed), bitness, OS, debug/release, size of the bitmap etc. to the question so that we can reproduce it? – Thomas Weller Dec 06 '15 at 19:30
  • @ThomasWeller Sure, .NET 3.5, 64bit, Windows Server 2008 R2, bitmap is now not a file but just a 400x400 bmp I created in code (see revised code). Installed .NET I have 3.5, 4.0, 4.5.2, 4.6.1 – dmeglio Dec 06 '15 at 19:32
  • Instead of `new Bitmap(400,400)` could you please try using the `new Bitmap(filename)` constructor and load an existing PNG file? Just to check. – Mike Nakis Dec 06 '15 at 19:36
  • Also, have you tried explicitly stating the pixel format as in `new Bitmap( width, height, Drawing.Imaging.PixelFormat.Format32bppArgb )` – Mike Nakis Dec 06 '15 at 19:39
  • 1
    @MikeNakis my code is actually using the filename overload. It was suggested by someone here to just generate a bitmap to make sure it wasn't something specific to the png I was loading. It doesn't seem to matter what overload I use, I just can't save pngs. – dmeglio Dec 06 '15 at 20:02
  • 1
    For whatever it's worth, I copied/pasted your code into a new console-mode project on one of my versions of MSVS ... and it worked fine. So (like you said at the outset), there seems to be "something odd" with that environment on that one particular machine.Q: Have you tried "release" vs. "debug" builds? Or tried building with different .Net profiles? MOST IMPORTANT: Q: Have you copied the test .exe to a different machine and tried it there? I'm definitely curious what you learn... – paulsm4 Dec 07 '15 at 01:28
  • @paulsm4 I thought it would. Yes, I have copied the test exe. It works on my local development PC, fails on the production server. On my test exe I have tried debug/release, however on my actual ASP.NET application only release. I agree something is odd with that machine, however since it's a GDI+ error being generated I was hoping maybe someone who's a bit more of a GDI+ expert than I am might know. I know the C# side, the underlying GDI+ is a mystery to me... – dmeglio Dec 07 '15 at 15:29
  • A few other (admittedly "last resort") options to try. If only for debugging purposes: 1) add a "sleep()" before (and possibly after) bmp.Save(); 2) substitute explicit "bmp.Dispose()" for implicit "using". – paulsm4 Dec 07 '15 at 22:33
  • What OS does the production server have? I'm guessing that it's different to all the other machines you've tried it on. Get machine/vm you can install this OS and the IDE on and debug it there. – ChrisF Dec 08 '15 at 11:45
  • @ChrisF Production is Win 2008 R2, my QA server is running the same OS. It works in QA. – dmeglio Dec 08 '15 at 14:44
  • Common use case is you're trying to open a file which containing directory does not exist. – vidstige Jan 09 '16 at 20:45
  • @vidstige My question involves only `MemoryStream`s. There are no files nor directories involved. – dmeglio Jan 11 '16 at 22:15
  • 1
    Did you ever resolve this? I have a very similar problem, with the difference that I get the error intermittently. I have two IIS sites running on the same server with the same code under the same user and one site errors and the other does not. IIS restart usually fixes it. – Joel Jan 05 '17 at 19:13
  • @Joel Nope, I'm afraid not. I wound up just switching to JPG to resolve the issue. – dmeglio Jan 07 '17 at 19:55

1 Answers1

6

The .NET reference source code here, in the save-to-stream case, gets a status value from a call to native method GdipSaveImageToStream:

public void Save(Stream stream, ImageCodecInfo encoder, EncoderParameters encoderParams) {

    ...

    if (!saved)
    {
        status = SafeNativeMethods.Gdip.GdipSaveImageToStream(new HandleRef(this,nativeImage),new UnsafeNativeMethods.ComStreamFromDataStream(stream),ref g,new HandleRef(encoderParams, encoderParamsMemory));
    }

    ...

}

this status value is the only API return value used to throw an exception from that method. When we look further into the StatusException function which decides what kind of exception to throw based on the status code, we find only a single possible status value to result in the ExternalException you got (from Gdiplus.cs, line 3167):

switch (status)
{
    case GenericError:
        return new ExternalException(SR.GetString(SR.GdiplusGenericError), E_FAIL);

    ...
}

0x80004005 is "unspecified error", and the SR.GdiplusGenericError is the text "A generic error occurred in GDI+." you got. This rules out several other possibilities we might suspect (which would result in different exceptions), namely:

  • out of memory
  • object busy
  • insufficient buffer
  • win32error
  • valueoverflow
  • unknownimageformat
  • property not found/not supported
  • unsupportedgdiplusversion

The native method resides in gdiplus.dll. So long story short, get your production server patched, .NET framework repaired. More details:

  1. compare the versions of that dll in %windir%\system32 between known-good machines and the production machine. The dll has hundreds of dependencies, so even if the version of the file itself matches, see to get your OS patched.
  2. the built-in codec for the PNG format is part of the WIC component of windows and resides in WindowsCodecs.dll and WindowsCodecsExt.dll - check the versions of those libraries as well. The registry key you mentioned should also point to WindowsCodecsExt.dll.
  3. not based on recherche, just ideas: do you access the production server through virtualization/remote desktop connection? Try a console session if you can. Try different screen resolutions and color depths. Try debug/release builds. Make sure you actually have cleared the DEBUG check in your release build configuration. Try builds x64 and MSIL. If you use NGEN on production, try without.
Cee McSharpface
  • 8,493
  • 3
  • 36
  • 77
  • I appreciate the thorough info. Definitely learned some things there. I will see if I'm able to confirm any of these things. In the meantime I had since switched to JPEG because it was a production app and I needed it to work. However, I do want to see if I can't get PNG to work as it would be a better solution. – dmeglio Jan 11 '16 at 22:14
  • my next suggestion will be to try an odd number of pixels in the new Bitmap(...) constructor :-) – Cee McSharpface Jan 12 '16 at 20:39