Marshal.Copy方法在C#.NET中抛出AccessViolationException

时间:2009-03-24 10:29:28

标签: c# bitmap access-violation

我正在开发一个可以显示摄像头实时图像的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;
    }

8 个答案:

答案 0 :(得分:9)

在我看来,你总是试图将 myByteArray.Length 的字节数复制到位图缓冲区。

你没有检查位图缓冲区实际上是那么大 - 所以可能会写下位图缓冲区的末尾。

尝试检查 myByteArray.Length 是否大于 bmpData.Stride x bmp.Height

如果是这种情况,您需要重新审视您对宽度,高度和像素格式的硬编码值所做的假设。

答案 1 :(得分:6)

您不应该立即复制整个图像。位图对象的内存分配可能不是您所期望的。例如,第一扫描线可以最后存储在存储器中,这意味着第二扫描线的数据将在位图对象的分配的存储区域之外结束。此外,扫描线之间可能有填充,以将它们放在偶数地址上。

一次复制一行,使用bmpData.Stride查找下一个扫描行:

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

答案 2 :(得分:0)

回答我:忘了 - &gt;

        // 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运行。有任何想法吗 我试图把这是一个新项目,如果我按下按钮时附加到进程,并按下此按钮多次发生错误,但在我的代码中我只调用一次,无论哪种方式都不确定为什么错误。

答案 3 :(得分:0)

也许

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

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

答案 4 :(得分:0)

我正在搜索一下,如果我们跳过数组大小不合适的可能性,我们以Remarks for BitmapData.Stride Property结尾:

  

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

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

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

它指出了我herehere

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

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

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

答案 5 :(得分: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对象在完成后正确处理。我偶尔会看到使用非托管代码进行操作但之后没有清理过的对象的奇怪结果。

此外,跨线程转换对象有时会很棘手。请参阅How to: Make Thread-Safe Calls to Windows Forms ControlsThread-Safe Calls Using Windows Form Controls in C#文章。

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

我个人看到了一些堆损坏(调试崩溃转储),因为使用了.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对于自下而上的位图可能是负的。

示例microsoft:confusion_matrix

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