2014-02-03 2 views
0

Это, вероятно, не очень хорошо сформулированный вопрос, потому что я не уверен, что происходит, поэтому я не знаю, как его спросить. Я пытаюсь учиться, и я надеюсь, что смогу получить какое-то направление. Ваше терпение с неофитом оценено.Два разных GCHandle ссылаются на тот же массив в памяти

У меня есть часть кода, которую я модифицирую. Он отображает изображение. Я хочу изменить изображение и отобразить его в другом окне. Я копирую код, который отображает изображение, выполняет модификацию и отображает измененное изображение как для исходного, так и для измененного изображения.

Кажется, что 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

Что вы делаете, определенно небезопасно. Зачем ты это делаешь? Есть ли причина, по которой вы так комфортно покидаете безопасную, управляемую среду?

Растровое изображение создается вокруг этого byte[]. Это нормально, если вы не против того, чтобы в управляемой памяти был закреплен byte[] (все в порядке на несколько секунд, а не на время приложения и т. Д.). Однако на следующей строке вы отпустите указатель!

Затем вы используете тот же byte[], его модифицируете и используйте для другого растрового изображения. Бум, это все тот же массив байтов. Не удивительно, что оба растровых изображения имеют одинаковый контент - вы просили об этом.

Причина, по которой она иногда срабатывает, а иногда и нет, заключается в том, что если ручка не перемещается GC, оба растровых изображения будут правильными. Однако, если GC перемещает массив байтов, битмапы не имеют возможности регулировки - они все равно будут указывать на то же место в памяти (что теперь неправильно).

Что вам нужно понять, так это то, что GCHandle не создает новый объект. Он просто инструктирует GC не вмешиваться в физическое местоположение (ну, в виртуальной памяти, но ...), пока существует GCHandle. Если вы хотите создать новый объект, сделайте что-то вроде byte[].Clone(). Тем не менее, вам все равно придется иметь ручку, закрепленную за всю жизнь Bitmap, что обычно плохое. Вместо этого попробуйте создать Bitmap обычным способом, затем выполните Bitmap.LockBits, затем используйте Marshal.Copy, чтобы скопировать массив растровых изображений в неуправляемую память растрового изображения, и все готово, красивое и относительно безопасное.

Вот фрагмент кода, который иллюстрирует всю концепцию:

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. Это по-прежнему довольно высокоуровневый, но он объясняет больше, чем этот ответ сам по себе.

+0

Я не знал, что Display.Image не создал собственную копию растрового изображения. Многое я не понимаю здесь (управляемая среда, закрепленная, проблема с выпуском указателя, ...). Я попытался изменить масштаб на новый массив, scaled1. Но это не работает. Не масштабируется1 в другом месте, чем масштабируется? Предложения? – DanG

+0

@DanG Это зависит от того, как вы создаете 'scaled1' - если вы просто выполняете' scaled1 = scaled', вы просто назначаете новый указатель на ту же ячейку памяти. Вы должны использовать, например, 'new byte []' и скопировать данные, или, вернее, 'scaled1 = (byte []) scaled.Clone()'. Тем не менее, это по-прежнему плохая практика, как я описал выше - растровое изображение будет ссылаться на вашу ячейку памяти - и не знает о реорганизации GC управляемой памяти. Если вы не работаете с растровым изображением на короткое время, вы не хотите этого делать. – Luaan

+0

Спасибо, это работает! Многому научиться здесь, читая много. Можете ли вы сказать несколько слов об управляемой v. Неуправляемой памяти? – DanG