Domanda

This is probably not a well-phrased question, because I am not sure what is happenning, so i don't know how to ask it pointedly. I am trying to learn, and i hope i can get some direction on this. Your patience with a neophyte is appreciated.

I have a piece of code i am modifying. It displays an image. I want to modify the image, and display it in a different window. I copy the code that displays the image, do the modification, and it displays the modified image for both the original and modified images.

It seems GCHandle keeps referring to the same memory? Am i not really making a new handle by changing the handle name? Sorry for the long piece of code, but i am just lost.

What is going wrong?

Most perplexing is that it was working, then i changed something, and now can't get back to the working version, tho i think my code is the same as the one that worked. Some setting some where?

 System.Runtime.InteropServices.GCHandle gch3 = System.Runtime.InteropServices.GCHandle.Alloc(scaled, System.Runtime.InteropServices.GCHandleType.Pinned);

 int pitch = mImageWidth;
 if (pitch % 4 != 0)
     pitch = ((pitch / 4) + 1) * 4;

 System.Drawing.Bitmap bitmap = new Bitmap(mImageWidth, mImageHeight, pitch, System.Drawing.Imaging.PixelFormat.Format8bppIndexed, gch3.AddrOfPinnedObject());

 gch3.Free();

 if (pal == null)
     {
         System.Drawing.Imaging.ColorPalette cp = bitmap.Palette;
         for (i = 0; i < cp.Entries.Length; ++i)
             {
                  cp.Entries[i] = Color.FromArgb(i, i, i);
             }

         pal = cp;
      }

  bitmap.Palette = pal;

  FirstImageDisplay.Image = bitmap;

  //second image here
  for (i = 0; i < frame.Length; ++i)
      scaled[i] = (byte)(.5 * scaled[i]);

  System.Runtime.InteropServices.GCHandle gch4 = System.Runtime.InteropServices.GCHandle.Alloc(scaled, System.Runtime.InteropServices.GCHandleType.Pinned);

  int pitch1 = mImageWidth;

  if (pitch1 % 4 != 0)
      pitch1 = ((pitch1 / 4) + 1) * 4;

  System.Drawing.Bitmap bitmap2 = new Bitmap(mImageWidth, mImageHeight, pitch, System.Drawing.Imaging.PixelFormat.Format8bppIndexed, gch4.AddrOfPinnedObject());

  gch4.Free();

  if (pal == null)
      {
          System.Drawing.Imaging.ColorPalette cp = bitmap.Palette;

          for (i = 0; i < cp.Entries.Length; ++i)
              {
                 cp.Entries[i] = Color.FromArgb(i, i, i);
              }

           pal = cp;
      }

  bitmap.Palette = pal;
  SecondImageDisplay.Image = bitmap;
  //end second image code
È stato utile?

Soluzione

What you're doing definitely isn't safe. Why are you doing this? Is there a reason you're so comfortable leaving the safe, managed environment?

The bitmap is created around that byte[]. This is okay as long as you don't mind having a pinned byte[] in the managed memory (okay for a few moments, not really for the duration of the application etc.). However, on the very next line, you release the pointer!

Then you use the same byte[], modify it, and use it for another bitmap. Boom, it's still the same byte array. It shouldn't be surprising that both bitmaps have the same content - you asked for that.

The reason why it sometimes works and sometimes it doesn't is that if the handle isn't moved by the GC, both bitmaps will be correct. However, if the GC moves the byte array, the Bitmaps have no way of adjusting - they will still point to the same location in memory (which is now wrong).

What you have to understand is that a GCHandle doesn't create a new object. It just instructs the GC not to mess with the physical location (well, in virtual memory, but...) as long as the GCHandle exists. If you want to create a new object, do something like byte[].Clone(). However, you're still going to have to have the handle pinned for all the lifetime of the Bitmap, which is usually a bad idea. Instead, try creating the Bitmap the usual way, then doing Bitmap.LockBits, then use Marshal.Copy to copy the bitmap array to the unmanaged memory of the Bitmap, and you're done, nice and relatively safe.

Here's a code snippet that illustrates the whole concept:

byte[] data = new byte[320 * 200 * 1];

Bitmap bmp1 = new Bitmap(320, 200, 
       System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
Bitmap bmp2 = new Bitmap(320, 200, 
       System.Drawing.Imaging.PixelFormat.Format8bppIndexed);

var bdata = bmp1.LockBits(new Rectangle(new Point(0, 0), bmp1.Size), 
                 ImageLockMode.WriteOnly, bmp1.PixelFormat);
try
{
    Marshal.Copy(data, 0, bdata.Scan0, data.Length);
}
finally
{
    bmp1.UnlockBits(bdata);
}

// Do your modifications

bdata = bmp2.LockBits(new Rectangle(new Point(0, 0), bmp2.Size), 
             ImageLockMode.WriteOnly, bmp2.PixelFormat);
try
{
    Marshal.Copy(data, 0, bdata.Scan0, data.Length);
}
finally
{
    bmp2.UnlockBits(bdata);
}

This isn't the best code performance-wise (it does need some copying), but the only real alternative is to use unsafe code - which you really shouldn't be doing, given your current apparent knowledge about the managed environment. It can lead to some nasty issues if you don't use it properly. In any case, the performance gains might be quite negligible - find out if you actually care before you go the unsafe way.

For more information about the problem and the complexities of working with managed and unmanaged memory, you can have a look at my blog at http://www.luaan.cz/2014/07/a-quick-introduction-to-managed-and.html It's still rather high-level, but it explains more than this answer on its own.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top