2013-02-09 11 views
5

私はSkyrimのセーブゲームマネージャーを作成中で、問題が発生しました。 SaveGameオブジェクトを単独で作成すると、保存のビットマップ部分が正しく機能します。しかし、そのメソッドをループで呼び出すと、ビットマップは誤った値をとります。主に別のセーブ・ゲームに似ています。ビットマップ内のデータが範囲外になるのはなぜですか?

TL; DR - 埋め込まれている画像以外のキャラクターセーブ用の正しい情報がフォームのリストボックスに表示されるのはなぜですか?正しい画像を選択するのではなく、最後に処理された画像を選択するように見えます。開いているファイルダイアログで選択したときとプロセスがどのように異なるのですか?


編集:アップデート - 私は、各セーブデータオブジェクトに格納されているビットマップに見て、scanDirectoryForSavesでセーブデータの作成中に何とかそれをめちゃくちゃにされることがわかりました。ビットマップにオブジェクトスコープの問題があり、わからないバイトポインタを使用していますか?ここで


は私のセーブゲームオブジェクトの静的なファクトリのためのコードです:私のフォームで

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

、私は特定のディレクトリ内のすべてのセーブファイルを読み取る方法を使用し、外のセーブデータオブジェクトを作成しますそれらを文字の名前に基づいて辞書に格納します。

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

私はリストボックスにキーを追加します。リストボックスで名前を選択すると、その文字の最新の保存がフォームに表示されます。名前、日付、およびその他のフィールドは各文字に適していますが、ビットマップはゲームの画像を保存する特定の文字のバリエーションです。

開いているファイルダイアログボックスとリストボックスからの保存を選択する際に、同じ方法を呼び出してフォームフィールドを更新します。あなたがIntPtrを取るconstructorを使用してBitmapを作成すると

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

答えて

4

IntPtrBitmapオブジェクトの存続期間中有効のままメモリのブロックを指している必要があります。メモリブロックが移動または割り当て解除されないようにする責任があります。

ただし、コードはIntPtrを指し、fileを指します。これは管理されたバイト配列です。 を参照するものはありません。ReadSaveGameが返されたため、ガベージコレクタは自由にメモリを再利用し、次のファイルに再利用できます。結果:破損したビットマップ。

GCHandleでメモリに配列を固定することでこの問題を解決できますが、Bitmapに独自のメモリを管理させる方が簡単で安全です。まず、空のBitmapを作成し、そのビットを設定します。ただ、それはバイト[]ファイル `に割り付けられているため、関数が終了したとき、私は私のコードでは、ファイルの内容がスコープの外に出る、理解を確認するために

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

を= File.ReadAllBytes(Filename); '?答えてくれてありがとう! – Gilbrilthor

+0

はい。 'file'はバイト配列への唯一の参照です。したがって、メソッドが返ってくると、いつでもガベージコレクタはメモリを解放することができます。 (あなたの 'IntPtr'は配列を指していますが、マネージ*リファレンスではないのでガベージコレクタはそれを無視します。)しかし、GCはすぐには動作しないかもしれません。 –

関連する問題