Marshal.Copy 方法在 C#.NET 中抛出 AccessViolationException
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/676814/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Marshal.Copy method throws AccessViolationException in C#.NET
提问by
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?
我正在开发一个 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;
}
回答by morechilli
It looks to me like you are always trying to copy the number of bytes myByteArray.Lengthto the bitmap buffer.
在我看来,您总是试图将字节数myByteArray.Length复制到位图缓冲区。
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.Lengthis ever greater than bmpData.Stride x bmp.Height
尝试检查myByteArray.Length是否大于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.
如果是这种情况,您将需要重新审视您对宽度、高度和像素格式的硬编码值所做的假设。
回答by Guffa
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:
一次复制一行,使用 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;
}
回答by jjfait
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.aspxwhich is:
有没有人想过这个?这是关于没有答案的第四页。使用来自 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);
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.
这在优化模式下不起作用并且在 IDE 中作为 exe 运行。我试图提出的任何想法都是一个新项目,如果我在按下按钮时附加到进程,并多次按下此按钮会发生错误,但在我的代码中我只调用一次,无论哪种方式都不知道为什么错误。
回答by Andrzej Nowak
Maybe
也许
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
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.
有无效的只写参数 - 在 ImageLockMode 中尝试 ReadWrite 或 ReadOnly?也许这有帮助。
回答by firda
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:
我搜索了一下,如果我们跳过数组大小不合适的可能性,我们会在BitmapData.Stride 属性的备注中结束:
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:
但我想知道:我们已经创建了位图: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);
And it pointed me hereand 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.
stride [in]
类型:INT
整数,指定一个扫描行的开头和下一个扫描行之间的字节偏移量。这通常是(但不一定)像素格式中的字节数(例如,2 表示每像素 16 位)乘以位图的宽度。传递给此参数的值必须是四的倍数。
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.
过0是什么意思?不知道,没找到。任何人都可以吗?可以使用负步幅创建位图吗?(由 .NET 提供new Bitmap(Width, Height, PixelFormat.Format24bppRgb)
)。无论如何,我们至少需要检查BitmapData.Stride。
回答by JamieSee
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.
我在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;
}
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.
您应该做的另一件事是确保在完成 Bitmap 对象时正确处理它。我偶尔会看到用非托管代码操作但之后没有清理的对象的奇怪结果。
Also, transiting objects across threads can be tricky sometimes. See How to: Make Thread-Safe Calls to Windows Forms Controlsand Thread-Safe Calls Using Windows Form Controls in C#article.
此外,有时跨线程传输对象可能会很棘手。请参阅如何:对 Windows 窗体控件进行线程安全调用和使用 C# 中的Windows 窗体控件进行线程安全调用一文。
回答by panpernicek
Lets try to change ThreadApartmentState to Single Threaded.
让我们尝试将 ThreadApartmentState 更改为 Single Threaded。
Also, check for cross-thread operations causing this errors.
此外,请检查导致此错误的跨线程操作。
回答by juFo
I've personally seen some heap corruptions (debugging the crash dumps) because .Length was used.
我个人见过一些堆损坏(调试故障转储),因为使用了 .Length。
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:
堆损坏的解决方案是以不同的方式计算 .Length:
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.
bytes 和 .Length 有 1 个字节的差异,导致堆损坏。
Math.Abswas taken directly from the example of Microsoft. Because the Stride can be negative for a bottom-up bitmap.
Math.Abs直接取自微软的例子。因为对于自下而上的位图,步幅可能为负。
Example microsoft: https://msdn.microsoft.com/en-us/library/system.drawing.imaging.bitmapdata.scan0%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396#Examples
微软示例: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.)
(+ 不要忘记 .Unlock 并将其添加到 try-finally 语句中。)