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

发布于 2024-07-15 11:44:09 字数 828 浏览 10 评论 0原文

我正在开发一个 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;
    }

I am working on a C# application that would display live images from a camera.
The problem I am facing with the following code snippet is that, I get AccessViolationException in Marshal.Copy when running this function executed continuously in a thread. But, this runs successfully when run once (I get a single static image). I guess it has to do with some memory corruption issue. Any idea/suggestions on how to deal with this problem?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

相守太难 2024-07-22 11:44:10

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

您没有检查位图缓冲区实际上有那么大 - 因此可能会注销位图缓冲区的末尾。

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

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

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.

沧桑㈠ 2024-07-22 11:44:10

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

一次复制一行,使用 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;
}

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;
}
窝囊感情。 2024-07-22 11:44:10

我个人见过一些堆损坏(调试故障转储),因为使用了 .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);

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

Math.Abs​​直接取自微软的例子。
因为对于自下而上的位图,步长可以为负。

微软示例: 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 语句中。)

I've personally seen some heap corruptions (debugging the crash dumps) because .Length was used.

As in:

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

The solution to the heap corruption was to do calculate the .Length differently:

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

bytes and .Length had 1 byte difference, resulting in the heap corruption.

Math.Abs was taken directly from the example of Microsoft.
Because the Stride can be negative for a bottom-up bitmap.

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

(+ don't forget the .Unlock and add it in a try-finally statement.)

挽清梦 2024-07-22 11:44:10

为我回答:忘记了 ->

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

Answer for me: forgot to ->

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

Has anyone figured this out? This is about the fourth page without an answer. Using the exact code from msdn: http://msdn.microsoft.com/en-us/library/system.drawing.imaging.bitmapdata.aspx which is:

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

This doesn't work in optimize mode and running as exe not in IDE. Any ideas
I tried to put this is a new project and if I attached to process when i push a button, and press this button multiple times the error occurs, but in my code I'm only calling once, either way not sure why the error.

记忆で 2024-07-22 11:44:10

也许

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

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

Maybe

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

has invalid argument for write only - try ReadWrite, or ReadOnly in ImageLockMode?
Maybe that helps.

巨坚强 2024-07-22 11:44:10

我搜索了一下,如果我们跳过数组大小不正确的可能性,我们将以 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
);

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

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

I was searching a bit and if we skip the possibility that the array is not of the proper size, we end in Remarks for BitmapData.Stride Property:

The stride is the width of a single row of pixels (a scan line),
rounded up to a four-byte boundary. If the stride is positive, the
bitmap is top-down. If the stride is negative, the bitmap is
bottom-up.

So, maybe we should do it this way:

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

But I wondered: We have created the bitmap: new Bitmap(Width, Height, PixelFormat.Format24bppRgb); ...so, how could it possibly be negative?
Time for 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);

And it pointed me here and here:

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

stride [in]
Type: INT
Integer that specifies the byte offset between the beginning of one scan line and the next. This is usually (but not necessarily) the
number of bytes in the pixel format (for example, 2 for 16 bits per
pixel) multiplied by the width of the bitmap. The value passed to this
parameter must be a multiple of four.

What does it mean to pass 0? Don't know, couldn't find it. Can anybody? Can the bitmap be created with negative stride? (by .NET new Bitmap(Width, Height, PixelFormat.Format24bppRgb)). In any case, we need to at least check BitmapData.Stride.

孤独岁月 2024-07-22 11:44:10

我在 BitmapSource 类。 似乎值得尝试使用下面的代码创建一个数组,并尝试多次执行复制方法而不发生爆炸。 如果可以的话,这很可能是一个数组大小问题。 如果相机中的数据与内存中位图的大小不匹配,您可能必须逐行复制数据。

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

    return rawImage;
}

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

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

I found the rawStride formula in the code example for the BitmapSource Class. It seems worth trying creating an array using the code below and attempting to execute your copy method multiple times without it bombing. If you can there is a fair chance that this is an array sizing problem. If the data from your camera doesn't match the size of the bitmap in memory, you'll probably have to copy the data line by line.

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

    return rawImage;
}

The other thing you should do is make sure that Bitmap object is being Disposed properly when you finish with it. I have occasionally seen weird results with objects that have been operated on with unmanaged code and not cleaned up afterwards.

Also, transiting objects across threads can be tricky sometimes. See How to: Make Thread-Safe Calls to Windows Forms Controls and Thread-Safe Calls Using Windows Form Controls in C# article.

眼泪淡了忧伤 2024-07-22 11:44:10

让我们尝试将 ThreadApartmentState 更改为单线程。

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

Lets try to change ThreadApartmentState to Single Threaded.

Also, check for cross-thread operations causing this errors.

冰之心 2024-07-22 11:44:10

它发生在我身上...
bmpData.Scan0 指向位图的第一行并且
bmpData.Stride 是一行的字节数。 大多数时候它是负数。
这意味着位图的第二行位于该地址
bmpData.Scan0 - MAth.abs(bmpData.Stride)
位图的第 n 行位于该地址处
bmpData.Scan0 - n * Math.Abs​​(bmpData.Stride)
因此,如果您从 bmpData.Scan0 复制不止一行,则会出现异常

It happens to me...
bmpData.Scan0 point to the first line of the bitmap and
bmpData.Stride is the number of bytes of a line. It is most of the time a negative number.
it means that the second line of the bitmap is at the address
bmpData.Scan0 - MAth.abs(bmpData.Stride)
the n line of the bitmap is at the address
bmpData.Scan0 - n * Math.Abs(bmpData.Stride)
so if you copy from bmpData.Scan0 more than one line , you have an exception

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文