4

I have some code which has the error "AccessViolationException was unhandled by user code: Attempted to read or write protected memory..."

A trimmed down version of the offending function is as follows:

protected override void OnPaint(PaintEventArgs pe)
{
    if ((updatingFastBackground) || (Calculating)) return; //ADDED FOR DEBUGGING, SEE BELOW
            BitmapData canvasData = Canvas.LockBits(new Rectangle(Point.Empty, Canvas.Size), ImageLockMode.WriteOnly, FastPixelFormat);
            BitmapData fbgData = fastBackground.LockBits(new Rectangle(Point.Empty, fastBackground.Size), ImageLockMode.ReadOnly, FastPixelFormat);
            try
            {
                unsafe
                {
                    byte* canvasDataScan0 = (byte*)canvasData.Scan0.ToPointer();
                    byte* fbgDataScan0 = (byte*)fbgData.Scan0.ToPointer();
                    Rectangle spriteBounds = new Rectangle(Point.Empty, ButtonImages.ImageSize);
                    for (int i = 0; i < ButtonImages.Images.Count; i++)
                    {
                        // Button offset location
                        Point l = new Point(
                            (int)((i % columnCount) * hStep + myIVM.Location.X),
                            (int)((i / columnCount) * vStep + myIVM.Location.Y));
                        // Paint at current location?
                        if (buttonPaintBounds.Contains(l))
                        {
                            BitmapData spriteData = buttonBitmaps[i].LockBits(spriteBounds, ImageLockMode.ReadOnly, FastPixelFormat);
                            try
                            {
                                int spriteLeft = Math.Max(l.X, 0);
                                int spriteRight = Math.Min(l.X + ButtonImages.ImageSize.Width, canvasData.Width);
                                int spriteTop = Math.Max(l.Y, 0);
                                int spriteBottom = Math.Min(l.Y + ButtonImages.ImageSize.Height, canvasData.Height);
                                int spriteWidth = spriteRight - spriteLeft;
                                int spriteHeight = spriteBottom - spriteTop;
                                byte* canvasRowLeft = canvasDataScan0 + (spriteTop * canvasData.Stride) + spriteLeft * 4;
                                byte* spriteRowLeft =
                                    (byte*)spriteData.Scan0.ToPointer() +
                                    Math.Max((spriteTop - l.Y), 0) * spriteData.Stride +
                                    Math.Max((spriteLeft - l.X), 0) * 4;
                                for (int y = 0; y < spriteHeight; y++)
                                {
                                    canvasRowLeft += canvasData.Stride;
                                    spriteRowLeft += spriteData.Stride;
                                    Byte* canvasWalk = (Byte*)canvasRowLeft;
                                    Byte* spriteWalk = (Byte*)spriteRowLeft;
                                    for (int x = 0; x < spriteWidth; x++)
                                    {
                                        if (spriteWalk[3] != 255)
                                        {
                                            canvasWalk[0] = (byte)(canvasWalk[0] * spriteWalk[3] / 255 + spriteWalk[0]);
                                            canvasWalk[1] = (byte)(canvasWalk[1] * spriteWalk[3] / 255 + spriteWalk[1]);
                                            canvasWalk[2] = (byte)(canvasWalk[2] * spriteWalk[3] / 255 + spriteWalk[2]);
                                        }
                                        canvasWalk += 4;
                                        spriteWalk += 4;
                                    }
                                }
                                thesePoints.Add(l);
                            }
                            finally
                            {
                                buttonBitmaps[i].UnlockBits(spriteData);
                            }
                        }

The error occurs on the line:

canvasWalk[0] = (byte)(canvasWalk[0] * spriteWalk[3] / 255 + spriteWalk[0]);

and even when replaced with:

canvasWalk[0] = 0;

The iteration variables y and x have different values each time it crashes so this leads me to believe an external function is modifying the Canvas bitmap.

IF this is in fact my problem, is there a way to prevent fastBackground and Canvas from being externally modified? I thought LockBits was supposed to do that that...

If that's not enough to answer, here's some more I've tried: I added the line

if ((updatingFastBackground) || (Calculating)) return;

to exit OnPaint if fastBackground Canvas or dimensions are being modified by other functions.

I could use a mutex to prevent the functions that modify the bitmaps fastBackground and Canvas from being run at the same time as paint (as I think they must be) but I'd rather block them another way as Canvas is public and I don't want to require passing a mutex out of the class.

per @usr 's suggestion, this further trimmed down version doesn't fail... Must have been a PTD error. (programmer too dumb) i.e. arithmetic error

protected override void OnPaint(PaintEventArgs pe)
{
    if ((updatingFastBackground) || (Calculating)) return; //ADDED FOR DEBUGGING, SEE BELOW
            BitmapData canvasData = Canvas.LockBits(new Rectangle(Point.Empty, Canvas.Size), ImageLockMode.WriteOnly, FastPixelFormat);
            BitmapData fbgData = fastBackground.LockBits(new Rectangle(Point.Empty, fastBackground.Size), ImageLockMode.ReadOnly, FastPixelFormat);
            try
            {
                unsafe
                {
                    byte* canvasDataScan0 = (byte*)canvasData.Scan0.ToPointer();
                    byte* fbgDataScan0 = (byte*)fbgData.Scan0.ToPointer();
                    Rectangle spriteBounds = new Rectangle(Point.Empty, ButtonImages.ImageSize);
                    for (int i = 0; i < ButtonImages.Images.Count; i++)
                    {
                        // Button offset location
                        Point l = new Point(
                            (int)((i % columnCount) * hStep + myIVM.Location.X),
                            (int)((i / columnCount) * vStep + myIVM.Location.Y));
                        // Paint at current location?
                        if (buttonPaintBounds.Contains(l))
                        {
                            BitmapData spriteData = buttonBitmaps[i].LockBits(spriteBounds, ImageLockMode.ReadOnly, FastPixelFormat);
                            try
                            {
                                byte* canvasRowLeft = canvasDataScan0;
                                byte* spriteRowLeft = (byte*)spriteData.Scan0.ToPointer();
                                for (int y = 0; y < 145; y++)
                                {
                                    canvasRowLeft += canvasData.Stride;
                                    spriteRowLeft += spriteData.Stride;
                                    Byte* canvasWalk = (Byte*)canvasRowLeft;
                                    Byte* spriteWalk = (Byte*)spriteRowLeft;
                                    for (int x = 0; x < 145; x++)
                                    {
                                        if (spriteWalk[3] != 255)
                                        {
                                            canvasWalk[0] = 0;
                                            canvasWalk[1] = 0;
                                            canvasWalk[2] = 0;
                                        }
                                        canvasWalk += 4;
                                        spriteWalk += 4;
                                    }
                                }
                                thesePoints.Add(l);
                            }
                            finally
                            {
                                buttonBitmaps[i].UnlockBits(spriteData);
                            }
                        }
AppFzx
  • 1,445
  • 2
  • 14
  • 23
  • The reason the title mentions `fixed` is I suspect I should be using `fixed` to lock some memory while editing the bitmaps. – AppFzx Feb 05 '13 at 20:24
  • 1
    I assume it is required to use [fixed](http://msdn.microsoft.com/en-us/library/f58wzh21%28VS.80%29.aspx) as a solution. You have to move their declaration into unsafe in that case... – rene Feb 05 '13 at 20:25
  • @rene would making `byte* canvasDataScan0` and `byte* fbgDataScan0` be enough or would that cause other hidden errors since it doesn't make the actual bitmaps fixed? – AppFzx Feb 05 '13 at 20:26
  • 1
    As I understand it, fixed tells the gc to not touch the memory that the vars reference. If you don't use fixed the gc can (and probably will) move your data around at will... – rene Feb 05 '13 at 20:29
  • @rene [I found someone else with a similar problem of bitmap being moved while locked here...](http://bytes.com/topic/c-sharp/answers/609972-bitmap-lockbits-fails-work-advertised) But the reply was that lockbits copies part of the bitmap and essentially makes that copied part `fixed`. – AppFzx Feb 05 '13 at 20:33
  • 1
    Maybe try fixed first and then see if it can be optimized? Maybe the canvasDataScan needs to be fixed.... – rene Feb 05 '13 at 20:48
  • 1
    Fixed is not required for the buffer returned by LockBits because it is unmanaged memory. Your pointer arithmetic is wrong. Find the bug (I wasn't able to comprehend all of the code so I couldn't). Create a simple repro. – usr Feb 05 '13 at 20:56
  • @rene That certainly was an idea but `void* cdScan0ToPointer = canvasData.Scan0.ToPointer` gives error: "You cannot use the fixed statement to take the address of an already fixed expression" i.e. lockbits does in fact make it fixed. – AppFzx Feb 05 '13 at 20:57
  • @usr that was the problem. Want to put that as an answer and I'll accept it? – AppFzx Feb 05 '13 at 21:07

1 Answers1

1

Moving my comment into an answer because it helped solve the problem:

Fixed is not required for the buffer returned by LockBits because it is unmanaged memory. Your pointer arithmetic is wrong. Find the bug. Create a simple repro to help find the bug.

usr
  • 168,620
  • 35
  • 240
  • 369