6

我正在开发一个可以显示来自相机的实时图像的 C# 应用程序。以下代码片段面临的问题是,当在线程中连续执行此函数时,我在 Marshal.Copy 中得到 AccessViolationException。但是,这在运行一次时会成功运行(我得到一个静态图像)。我想这与一些内存损坏问题有关。关于如何处理这个问题的任何想法/建议?

    private Image ByteArrayToImage(byte[] myByteArray) 
    {
        if (myByteArray != null)
        {
            MemoryStream ms = new MemoryStream(myByteArray);
            int Height = 504;
            int Width = 664;
            Bitmap bmp = new Bitmap(Width, Height, PixelFormat.Format24bppRgb);
            BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
            Marshal.Copy(myByteArray, 0, bmpData.Scan0, myByteArray.Length);
            bmp.UnlockBits(bmpData);

            return bmp;
        }
        return null;
    }
4

8 回答 8

10

It looks to me like you are always trying to copy the number of bytes myByteArray.Length to the bitmap buffer.

You are not checking that the bitmap buffer is in fact as big as that - so are probably writing off the end of the bitmap buffer.

Try checking if myByteArray.Length is ever greater than bmpData.Stride x bmp.Height

If this is the case you'll need to relook at the assumptions you've made with your hard coded values for width, height and pixel format.

于 2009-03-24T10:59:02.043 回答
6

You shouldn't copy the entire image at once. The memory allocation of the bitmap object might not be what you expect. For example the first scan line may be stored last in memory, which would mean that the data for the second scan line would end up outside the allocated memory area for the bitmap object. Also there may be padding between the scan lines to place them on an even address.

Copy one line at a time, using bmpData.Stride to find the next scan line:

int offset = 0;
long ptr = bmpData.Scan0.ToInt64();
for (int i = 0; i < Height; i++) {
   Marshal.Copy(myByteArray, offset, new IntPtr(ptr), Width * 3);
   offset += Width * 3;
   ptr += bmpData.Stride;
}
于 2009-03-24T11:02:13.720 回答
2

我个人见过一些堆损坏(调试故障转储),因为使用了 .Length。

如:

IntPtr ptr = bitmapdata.Scan0;
Marshal.Copy(pixeldata, 0, ptr, pixeldata.Length);

堆损坏的解决方案是以不同的方式计算 .Length :

IntPtr ptr = bitmapdata.Scan0;
int bytes = Math.Abs(bitmapdata.Stride) * bmp.Height;
Marshal.Copy(pixeldata, 0, ptr, bytes);

bytes 和 .Length 有 1 个字节的差异,导致堆损坏。

Math.Abs​​ 直接取自 Microsoft 的示例。因为对于自下而上的位图,Stride 可能是负数。

微软示例:https ://msdn.microsoft.com/en-us/library/system.drawing.imaging.bitmapdata.scan0%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396#Examples

(+ 不要忘记 .Unlock 并将其添加到 try-finally 语句中。)

于 2017-10-26T13:54:04.570 回答
0

让我们尝试将 ThreadApartmentState 更改为 Single Threaded。

此外,检查导致此错误的跨线程操作。

于 2014-08-14T10:07:55.393 回答
0

也许

BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);

只写的参数无效 - 在 ImageLockMode 中尝试 ReadWrite 或 ReadOnly?也许这有帮助。

于 2011-02-02T07:25:30.237 回答
0

回答我:忘了->

        // Unlock the bits right after Marshal.Copy
        bmp.UnlockBits(bmpData);

有没有人弄清楚这一点?这是没有答案的第四页。使用来自 msdn 的确切代码:http: //msdn.microsoft.com/en-us/library/system.drawing.imaging.bitmapdata.aspx 这是:

                        Bitmap bmp = new Bitmap("c:\\picture.jpg");

                        // Lock the bitmap's bits.  
                        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
                        System.Drawing.Imaging.BitmapData bmpData =
                            bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
                            bmp.PixelFormat);

                        // Get the address of the first line.
                        IntPtr ptr = bmpData.Scan0;

                        // Declare an array to hold the bytes of the bitmap.
                        int bytes = bmpData.Stride * bmp.Height;
                        byte[] rgbValues = new byte[bytes];

                        // Copy the RGB values into the array.
                        //This causes read or write protected memory
                        System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

这在优化模式下不起作用,并且在 IDE 中不作为 exe 运行。我试图提出的任何想法都是一个新项目,如果我在按下按钮时附加到进程,并多次按下此按钮会发生错误,但在我的代码中我只调用一次,无论哪种方式都不确定为什么错误。

于 2010-01-25T22:56:58.523 回答
0

我搜索了一下,如果我们跳过数组大小不合适的可能性,我们以 BitmapData.Stride 属性的备注结尾:

步幅是单行像素(扫描线)的宽度,四舍五入到四字节边界。如果步幅为正,则位图是自上而下的。如果步幅为负,则位图是自下而上的。

所以,也许我们应该这样做:

BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
Marshal.Copy(myByteArray, 0, bmpData.Scan0 + 
( bmpData.Stride >= 0 ? 0 : bmpData.Stride*(bmp.Height-1) ),
myByteArray.Length);

但我想知道:我们已经创建了位图:new Bitmap(Width, Height, PixelFormat.Format24bppRgb);......那么,它怎么可能是负面的?ILSpy
的时间:

public Bitmap(int width, int height, PixelFormat format)
{
    IntPtr zero = IntPtr.Zero;
    int num = SafeNativeMethods.Gdip.GdipCreateBitmapFromScan0(width, height, 0, (int)format, NativeMethods.NullHandleRef, out zero);
    if (num != 0)
    {
            throw SafeNativeMethods.Gdip.StatusException(num);
    }
    base.SetNativeImage(zero);
}

// System.Drawing.SafeNativeMethods.Gdip
[DllImport("gdiplus.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
internal static extern int GdipCreateBitmapFromScan0(int width, int height, int stride, int format, HandleRef scan0, out IntPtr bitmap);

它指向我这里这里

Bitmap(
  [in]  INT width,
  [in]  INT height,
  [in]  INT stride,
  [in]  PixelFormat format,
  [in]  BYTE *scan0
);

stride [in]
类型:INT
整数,指定一个扫描行的开头和下一个扫描行的开头之间的字节偏移量。这通常(但不一定)是像素格式中的字节数(例如,2 表示每像素 16 位)乘以位图的宽度。传递给此参数的值必须是四的倍数。

通过0是什么意思?不知道,没找到。有人可以吗?可以用负步幅创建位图吗?(通过 .NET new Bitmap(Width, Height, PixelFormat.Format24bppRgb))。无论如何,我们至少需要检查BitmapData.Stride

于 2014-08-13T12:30:42.253 回答
0

我在BitmapSource Class的代码示例中找到了 rawStride 公式。似乎值得尝试使用下面的代码创建一个数组并尝试多次执行您的复制方法而不会被轰炸。如果可以的话,这很有可能是一个数组大小问题。如果来自相机的数据与内存中位图的大小不匹配,您可能必须逐行复制数据。

private byte[] CreateImageByteArray(int width, int height, PixelFormat pixelFormat)
{
    int rawStride = (width * pixelFormat.BitsPerPixel + 7) / 8;
    byte[] rawImage = new byte[rawStride * height];

    return rawImage;
}

您应该做的另一件事是确保在完成 Bitmap 对象后正确处理它。我偶尔会看到使用非托管代码操作且事后未清理的对象的奇怪结果。

此外,有时跨线程传输对象可能很棘手。请参阅如何:对 Windows 窗体控件进行线程安全调用和在 C# 中使用 Windows 窗体控件进行线程安全调用一文。

于 2014-08-14T05:14:28.283 回答