将位图保存到内存流时发生了GDI+通用错误

11

我有些代码在几台机器上都能完美运行(开发、QA、UAT)。但是,生产环境中我遇到了“GDI+ 中出现了通用错误”问题,原因是在bmp.Save(ms, ImageFormat.Png);处。因此,我认为你们无法重现这个问题,但也许有人能发现我的错误。

需要注意的是,我已经搜索了很多常见解决方案,但由于保存到了MemoryStream中,所以大多数人建议的文件权限问题并不适用;而像“bmp在打开时被锁定”的解决方案也不适用,因为我正在其他地方写入它。最后,这不是因为png需要可寻址流,因为MemoryStream是可寻址的。

请注意,如果我将它更改为ImageFormat.Jpeg,它就可以正常工作。我只在处理PNG时遇到了问题。我发现注册表键HKEY_CLASSES_ROOT\CLSID\{FAE3D380-FEA4-4623-8C75-C6B61110B681}可能会因为权限问题而出现问题。因此,我设置了该键允许Everyone对该键进行读取,但没有任何改变。

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;
}

以下是完整的堆栈跟踪:

[ExternalException (0x80004005): GDI+ 中发生一般性错误。]
System.Drawing.Image.Save(Stream stream, ImageCodecInfo encoder, EncoderParameters encoderParams) +616457
WP.Tools.Img.GenerateImage(String text) +383

注意:我的问题已经列举了所提出的重复解决方案。 它们都不是问题的原因。 如果是这样,JPEG也会失败。


2
可能是GDI+异常将位图保存到内存流的重复问题。 - Thomas Weller
1
也许相关:https://dev59.com/gXNA5IYBdhLWcg3wNa6T?rq=1 - Thomas Weller
1
@Thomas Weller 我不认为这是同一个问题。首先,我没有在任何地方释放内存,其次,如果那是问题的话,我期望jpeg也会失败,而不仅仅是png。 - dmeglio
1
当您不加载bg.png而是在内存中创建一个新的位图时会发生什么?这样我们就可以缩小问题范围。 - usr
1
您能否在问题中添加.NET版本(已编译和安装的版本)、位数、操作系统、调试/发布模式、位图大小等信息,以便我们可以复现它? - Thomas Weller
显示剩余32条评论
1个回答

6
在将图像保存至流的情况下,.NET参考源代码此处从调用本机方法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));
    }

    ...

}

这个状态值是唯一用于从该方法抛出异常的API返回值。当我们进一步查看StatusException函数时,该函数根据状态码决定要抛出什么类型的异常,我们发现只有一个可能的状态值会导致您获得的ExternalException(来自Gdiplus.cs,第3167行):

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

    ...
}

0x80004005是“未指定的错误”,而SR.GdiplusGenericError是“在GDI+中发生通用错误”的文本。这排除了我们可能怀疑的几种其他可能性(会导致不同的异常),即:

  • 内存不足
  • 对象忙碌
  • 缓冲区不足
  • win32error
  • 值溢出
  • unknownimageformat
  • 未找到/不支持的属性
  • unsupportedgdiplusversion

本地方法位于gdiplus.dll中。长话短说,修补你的生产服务器,修复.NET框架。更多细节:

  1. 比较%windir%\system32中该dll的版本,以及已知良好机器和生产机器之间的差异。该dll有数百个依赖项,因此即使文件本身的版本匹配,也要确保获取操作系统的修补程序。
  2. PNG格式的内置编解码器是Windows的WIC组件的一部分,驻留在WindowsCodecs.dllWindowsCodecsExt.dll中-也要检查这些库的版本。您提到的注册表键也应指向WindowsCodecsExt.dll。
  3. 不基于搜索,只是想法:您通过虚拟化/远程桌面连接访问生产服务器吗?如果可以,请尝试控制台会话。尝试不同的屏幕分辨率和颜色深度。尝试调试/发布版本。确保您实际上已经在发布构建配置中清除了DEBUG检查。尝试x64和MSIL构建。如果您在生产中使用NGEN,则尝试不使用。

我很感激这些详尽的信息。肯定从中学到了一些东西。我会看看能否确认其中的任何内容。与此同时,由于这是一个生产应用程序,我已经转向使用JPEG格式,因为我需要它能够正常工作。然而,我确实想看看是否能够让PNG格式工作,因为那将是更好的解决方案。 - dmeglio
我的下一个建议是尝试在新的Bitmap(...)构造函数中使用奇数像素 :-) - Cee McSharpface

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接