尝试在垃圾回收后立即保存PictureBox.Image时出现AccessViolationException

时间:2017-11-10 01:44:36

标签: c# winforms gdi+

我们有一个应用程序,其中包含一个与摄像头连接的表单。用户可以从表单控制相机,用它拍照,在WinForms PictureBox中显示,打开另一个表单来编辑正在显示的图片或将该图片保存到磁盘。我们的客户一直在抱怨间歇性的AccessViolationExceptions。在一个下午的大部分时间之后,我找到了一种可靠的方法,通过在将GC.Collect保存到PictureBox.Image之前强制MemoryStream来重现我的开发环境中的问题。

我们填充PictureBox这样的控件:

int w = videoInfoHeader.BmiHeader.Width;
int h = videoInfoHeader.BmiHeader.Height;
int stride = w * 3;

GCHandle handle = GCHandle.Alloc(savedArray, GCHandleType.Pinned);
long scan0 = (long)handle.AddrOfPinnedObject();
scan0 += (h - 1) * stride;  //image is upside down, so start at the bottom (I guess bottom-up bitmaps still scan left-to-right?)
Bitmap b = new Bitmap(w, h, -stride, PixelFormat.Format24bppRgb, (IntPtr)scan0);
handle.Free();
Image old = pictureBog.Image;
pictureBox.Image = b;
if (old != null)
    old.Dispose();
//Show picturebox, and let user use the form

当用户想要编辑照片时,我们将位图拉出为格式化为Jpeg的MemoryStream(我不知道为什么):

//GC.Collect()
using (MemoryStream stream = new MemoryStream())
{
    pictureBox.Image.Save(stream, ImageFormat.Jpeg);  //AccessViolationException here if GC.Collect() is uncommented.
    byte[] pic = stream.ToArray();
    //Do stuff with pic and open editor form
}

我注意到,从Randomly occurring AccessViolationException in GDI+开始,GC存在从非托管代码中移出内容的问题,因此您必须“固定”托管对象以防止这种情况发生。我看到我们在填充PictureBox时正在执行此操作,但是当我们将图像从PictureBox中拉出时,我们就会这样做。我理解需要“固定”源字节数组,因为字节数组只是一个哑数组。但是,我甚至不知道在保存图像时我会尝试“固定” - MemoryStream?我认为Bitmap.Save(Stream)足够聪明,可以在需要时自动处理。加上缓冲区需要重新分配,因为它总是填满,所以钉扎对我来说没有多大意义。任何人都知道导致内存访问不良的原因是什么?

我认为最坏的情况是我们在提取图像数据时避免使用PictureBox,保留原始字节数组,从中构建Bitmap并从新副本中保存。这似乎有效,但我不知道这是否只是巧合,真正的问题仍然存在。

1 个答案:

答案 0 :(得分:0)

从字节数组中生成图像的一种更安全的方法是首先使用简单的new Bitmap(width, height, pixelformat)构造函数创建图像,然后使用LockBits打开其后备数据,然后将数据复制到它显示的Scan0指针,扫描线扫描线,使用Marshal.Copy。这样,你永远不会搞乱可能导致问题的非托管指针。涉及的唯一指针是LockBits保留的特定锁定内存,调用unlockBits后,再次安全地成为内部图像数据的一部分。

代码可以在这里找到:

A: Why must "stride" in the System.Drawing.Bitmap constructor be a multiple of 4?

(答案是一样的,但问题却截然不同,所以我认为将此标记为副本有很多用处)

请注意,方法中包含倒置步幅的处理。