调用Marshal.Copy提取位图区域时触发AccessViolationException如何解决
异常触发原因
- 最核心的原因是你误判了Stride符号:GDI+默认创建的位图大多是Bottom-Up存储格式,这类位图的Stride为负值,代表行扫描方向是从内存低位到高位对应图像从下到上。你使用
Math.Abs(Stride)计算行偏移,直接导致地址计算错误,访问了位图范围外的非法内存,触发访问冲突。 - 次要原因:局部锁定位图区域时,直接尝试复制
Stride * 高度的连续内存,实际上每行像素后跟随的填充字节不属于当前锁定区域的可访问范围,尤其是你锁定的是靠近位图底部的区域,一次性复制很容易超出内存边界。
规避方案
你需要修正行地址计算逻辑,同时仅复制每行的有效像素字节,不要处理填充位,修正后的代码如下:
Bitmap bitmap = new Bitmap(1741, 2141, PixelFormat.Format1bppIndexed); int zoneWidth = 50; int zoneHeight = 50; int zoneX = 168; int zoneY = bitmap.Height - zoneHeight; Rectangle zone = new Rectangle(zoneX, zoneY, zoneWidth, zoneHeight); BitmapData bitmapData = bitmap.LockBits(zone, ImageLockMode.ReadOnly, bitmap.PixelFormat); // 计算当前选中区域每行的有效字节数,1bpp格式为(宽度+7)/8 int rowValidByteCount = (zoneWidth + 7) / 8; byte[] pixels = new byte[zoneHeight * rowValidByteCount]; IntPtr scanStart = bitmapData.Scan0; for (int y = 0; y < zoneHeight; y++) { // 直接用原始Stride计算行地址,不要取绝对值,自动适配正/负Stride IntPtr rowAddr = IntPtr.Add(scanStart, y * bitmapData.Stride); Marshal.Copy(rowAddr, pixels, y * rowValidByteCount, rowValidByteCount); } bitmap.UnlockBits(bitmapData);
如果需要保留填充字节,只需要把上面的rowValidByteCount替换为Math.Abs(bitmapData.Stride)即可,同时数组长度也要对应调整,但这种场景下依然要保留直接用原始Stride计算行地址的逻辑。
不同像素格式的行有效字节数计算规则参考:
- 1bppIndexed:
(width + 7) / 8- 4bppIndexed:
(width + 1) / 2- 8bppIndexed:
width- 24bppRgb:
width * 3- 32bppRgb/Argb:
width * 4
内容的提问来源于stack exchange,提问作者Jens Meyer
相关产品推荐
相关产品推荐

