2013-02-09 13 views
5

Sto creando un gestore di giochi di salvataggio per Skyrim e ho riscontrato un problema. Quando creo un oggetto SaveGame da solo, la parte bitmap del salvataggio funziona correttamente. Quando chiamo quel metodo in un ciclo, tuttavia, la bitmap assume un valore errato, principalmente uno che è simile a quello di un altro salvataggio.Perché i dati in una bitmap sono fuori portata?

TL; DR - Perché la casella di riepilogo del mio modulo mostra informazioni corrette per un carattere salvato tranne per l'immagine che è incorporata? Piuttosto che scegliere l'immagine corretta, sembra selezionare l'ultimo elaborato. In che modo il processo è diverso da quando viene selezionato attraverso una finestra di dialogo di file aperti?


Edit: Aggiornamento - Ho guardato nelle bitmap memorizzati con ogni oggetto SaveGame e ha scoperto che durante la creazione dei salvataggi nei scanDirectoryForSaves è in qualche modo in disordine in su. Esiste un problema di ambito oggetto con bitmap e l'utilizzo di un puntatore di byte di cui non sono a conoscenza?


Ecco il codice per la fabbrica statica mia salvare dell'oggetto gioco:

public string Name { get; private set; } 
    public int SaveNumber { get; private set; } 
    public int PictureWidth { get; private set; } 
    public int PictureHeight { get; private set; } 
    public Bitmap Picture { get; private set; } 
    public DateTime SaveDate { get; private set; } 
    public string FileName { get; private set; } 

    public static SaveGame ReadSaveGame(string Filename) 
    { 

     SaveGame save = new SaveGame(); 
     save.FileName = Filename; 

     byte[] file = File.ReadAllBytes(Filename); 

     int headerWidth = BitConverter.ToInt32(file, 13); 
     save.SaveNumber = BitConverter.ToInt32(file, 21); 
     short nameWidth = BitConverter.ToInt16(file, 25); 

     save.Name = System.Text.Encoding.UTF8.GetString(file, 27, nameWidth); 
     save.PictureWidth = BitConverter.ToInt32(file, 13 + headerWidth - 4); 
     save.PictureHeight = BitConverter.ToInt32(file, 13 + headerWidth); 
     save.readPictureData(file, 13 + headerWidth + 4, save.PictureWidth, save.PictureHeight); 

     save.SaveDate = DateTime.FromFileTime((long)BitConverter.ToUInt64(file, 13 + headerWidth - 12)); 

     return save; 
    } 

    private void readPictureData(byte[] file, int startIndex, int width, int height) 
    { 
     IntPtr pointer = Marshal.UnsafeAddrOfPinnedArrayElement(file, startIndex); 
     Picture = new Bitmap(width, height, 3 * width, System.Drawing.Imaging.PixelFormat.Format24bppRgb, pointer); 
    } 

sulla mia forma, io uso un metodo per leggere tutti i file di salvataggio in una certa directory, creare oggetti Savegame su loro e li memorizzano in un dizionario basato sul nome del personaggio.

private Dictionary<string, List<SaveGame>> scanDirectoryForSaves(string directory) 
    { 
     Dictionary<string, List<SaveGame>> saves = new Dictionary<string, List<SaveGame>>(); 
     DirectoryInfo info = new DirectoryInfo(directory); 

     foreach (FileInfo file in info.GetFiles()) 
     { 
      if (file.Name.ToLower().EndsWith(".ess") || file.Name.ToLower().EndsWith(".bak")) 
      { 
       string filepath = String.Format(@"{0}\{1}", directory, file.Name); 
       SaveGame save = SaveGame.ReadSaveGame(filepath); 

       if (!saves.ContainsKey(save.Name)) 
       { 
        saves.Add(save.Name, new List<SaveGame>()); 
       } 
       saves[save.Name].Add(save); 
      } 
     } 

     foreach (List<SaveGame> saveList in saves.Values) 
     { 
      saveList.Sort(); 
     } 

     return saves; 
    } 

Aggiungere le chiavi a una casella di riepilogo. Quando un nome viene selezionato nella casella di riepilogo, sul modulo viene visualizzato l'ultimo salvataggio per il carattere. Il nome, la data e altri campi sono corretti per ogni carattere, ma la bitmap è una variante di un certo personaggio, salva l'immagine del gioco.

Sto chiamando lo stesso metodo per aggiornare i campi del modulo in entrambi selezionando un salvataggio da una finestra di dialogo di file aperti e dalla casella di riepilogo.

private void updateLabels(SaveGame save) 
    { 
     nameLabel.Text = "Name: " + save.Name; 
     filenameLabel.Text = "File: " + save.FileName; 
     saveNumberLabel.Text = "Save Number: " + save.SaveNumber; 

     saveDateLabel.Text = "Save Date: " + save.SaveDate; 

     saveGamePictureBox.Image = save.Picture; 
     saveGamePictureBox.Image = ScaleImage(
      saveGamePictureBox.Image, saveGamePictureBox.Width, saveGamePictureBox.Height); 
     saveGamePictureBox.Invalidate(); 
    } 

risposta

4

Quando si crea un Bitmap utilizzando il constructor che prende un IntPtr, il IntPtr deve puntare ad un blocco di memoria che rimane valido per tutta la vita dell'oggetto Bitmap. Sei responsabile di garantire che il blocco di memoria non venga spostato o deallocato.

Tuttavia, il codice sta passando un IntPtr che punta a file, che è un array di byte gestito. Poiché nulla riporta file dopo i ritorni ReadSaveGame, il garbage collector è libero di recuperare la memoria e riutilizzarla per il file successivo. Il risultato: bitmap danneggiati.

Sebbene sia possibile risolvere questo problema bloccando la matrice in memoria con GCHandle, è probabilmente più semplice e sicuro lasciare che sia lo Bitmap a gestire la propria memoria. In primo luogo creare un vuoto Bitmap, quindi impostare le sue punte:

private void readPictureData(byte[] file, int startIndex, int width, int height) 
{ 
    Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb); 
    BitmapData data = bitmap.LockBits(
     new Rectangle(0, 0, width, height), 
     ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); 
    Marshal.Copy(file, startIndex, data.Scan0, width * height * 3); 
    bitmap.UnlockBits(data); 
    Picture = bitmap; 
} 
+0

Giusto per essere sicuro di capire, nel mio codice, il contenuto del file escono di portata quando la funzione termina perché è stato assegnato nel file '[] byte = File.ReadAllBytes (Nome file); '? Grazie mille per la risposta! – Gilbrilthor

+0

Sì; 'file' è l'unico riferimento alla matrice di byte, quindi quando il metodo restituisce, il garbage collector è autorizzato a recuperare la memoria ogni volta che lo desidera. (Anche se il tuo 'IntPtr' punta all'array, non è un riferimento * gestito *, quindi il garbage collector lo ignora.) Ma il GC potrebbe non essere eseguito immediatamente, motivo per cui te ne sei scappato quando c'era un solo file. –

Problemi correlati