两个不同的GCHandle在内存中引用相同的数组

时间:2014-02-03 08:03:03

标签: c# image bitmap system.drawing unmanaged-memory

这可能不是一个措辞得很好的问题,因为我不确定发生了什么,所以我不知道如何有针对性地提出这个问题。我正在努力学习,我希望我能就此得到一些指导。您对初学者的耐心表示赞赏。

我有一段我正在修改的代码。它显示一个图像。我想修改图像,并在不同的窗口中显示它。我复制显示图像的代码,进行修改,并显示原始图像和修改图像的修改图像。

GCHandle似乎一直在指同一个记忆?我是不是真的通过更改手柄名称来制作新手柄?很抱歉这段代码很长,但我很遗憾。

出了什么问题?

最令人困惑的是它正在工作,然后我改变了一些东西,现在无法回到工作版本,我认为我的代码与工作的代码相同。有些设置在哪里?

 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

1 个答案:

答案 0 :(得分:1)

你所做的绝对不安全。你为什么做这个?您是否有理由离开安全,有管理的环境?

位图是围绕byte[]创建的。只要您不介意在托管内存中固定byte[](暂时还好,在应用程序的持续时间内等等),这是可以的。但是,在下一行,你释放指针!

然后使用相同的byte[],修改它,并将其用于另一个位图。 Boom,它仍然是相同的字节数组。两个位图具有相同的内容并不奇怪 - 你要求它。

它有时可以工作的原因有时并不是因为如果GC没有移动手柄,两个位图都是正确的。但是,如果GC移动字节数组,则位图无法调整 - 它们仍然指向内存中的相同位置(现在是错误的)。

您必须了解的是GCHandle不会创建新对象。只要GCHandle存在,它只是指示GC不要弄乱物理位置(好吧,在虚拟内存中,但是......)。如果要创建新对象,请执行byte[].Clone()之类的操作。但是,你仍然需要在Bitmap的所有生命周期中固定句柄,这通常是一个坏主意。相反,尝试以通常的方式创建Bitmap,然后执行Bitmap.LockBits,然后使用Marshal.Copy将位图数组复制到Bitmap的非托管内存,这样就完成了,很好并且相对安全。< / p>

这是一个代码片段,说明了整个概念:

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);
}

这不是性能最佳的代码(它确实需要一些复制),但唯一真正的替代方法是使用unsafe代码 - 鉴于您当前的明显知识,您实际上不应该这样做关于托管环境。如果你没有正确使用它,它可能会导致一些令人讨厌的问题。在任何情况下,性能提升可能都是微不足道的 - 在你采取不安全的方式之前,先了解你是否真正关心。

有关问题以及使用托管和非托管内存的复杂性的更多信息,您可以查看我的博客http://www.luaan.cz/2014/07/a-quick-introduction-to-managed-and.html它仍然是相当高级别的,但它解释的不仅仅是这个答案。自己的。