Marshal.Copy method throws AccessViolationException in C#.NET
Asked Answered
B

9

6

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;
    }
Beyrouth answered 24/3, 2009 at 10:29 Comment(1)
Not sure, but could you try putting a lock around 'LockBits' and 'UnlockBits'. As this statement locks bitmap on system memory. And you exception tells invalid access to protected memory.Logger
E
10

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.

Eratosthenes answered 24/3, 2009 at 10:59 Comment(0)
T
6

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;
}
Tantalous answered 24/3, 2009 at 11:2 Comment(3)
Maybe I'm overlooking something, but I fail to see any jumps or differences in the pointer for each line in your example. As far as I can see, this code would do exactly the same as Marshal.Copy(myByteArray, 0, bmpData.Scan0, myByteArray.Length); (the length would be bmpData.Stride * bmp.Height).Inveracity
@LouisSomers That only does the same thing in the special case where the image is stored top down in memory. The Stride might be negative. Also, there is padding between scan lines, but there might not be padding after the last scan line.Tantalous
bmpData.Stride will not change with each iteration, it is a fixed length during every iteration in the loop. It does not know if you are looping forward or backwards, and it does not count the times it has been called. In order for it to support fragmented storage it would have to be a method that takes a row index. The documentation also states that it "Gets or sets the stride width (also called scan width) of the Bitmap object.". It does not point to different locations every time it is called. Not a big deal, your code works, but a single call to Marshal.Copy should perform better.Inveracity
G
2

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

Gabrielson answered 26/10, 2017 at 13:54 Comment(1)
Excellent point. Though somehow aware what is Stride, I didn't use it in the length of data block. Solved my problem, which - oddly enough - appeared only in a tiny fraction of cases.Counterscarp
R
0

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.

Rodenticide answered 25/1, 2010 at 22:56 Comment(1)
You're ignoring the fact that one pixel is more than one byte depending on the pixel format in int bytes = bmpData.Stride * bmp.Height;Zoogeography
D
0

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.

Diaphoresis answered 2/2, 2011 at 7:25 Comment(0)
B
0

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.

Branchia answered 13/8, 2014 at 12:30 Comment(0)
H
0

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.

Hudgens answered 14/8, 2014 at 5:14 Comment(0)
P
0

Lets try to change ThreadApartmentState to Single Threaded.

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

Pacheco answered 14/8, 2014 at 10:7 Comment(1)
Hello. This should really be a comment not an answer (I know that you don't have enough rep to post comments yet).Phial
C
0

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

Cumshaw answered 25/1, 2023 at 13:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.