2013-02-09 14 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; 
    } 

我将键添加到列表框中。在列表框上选择名称时,该字符的最新保存将显示在表单上。每个角色的名称,日期和其他字段都是正确的,但是位图是保存游戏图片的某个角色的变体。

我打电话给同样的方法来更新表单字段在从打开的文件对话框以及列表框中选择保存。

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

当你使用需要一个IntPtrconstructor创建Bitmap,该IntPtr必须指向保持有效的Bitmap对象的生命周期的内存块。您有责任确保内存块不会被移动或释放。

但是,您的代码正在传递IntPtr指向file,这是一个托管字节数组。由于ReadSaveGame返回后没有任何内容引用file,因此垃圾回收器可以自由回收内存并将其重新用于下一个文件。结果:损坏的位图。

虽然你可以通过将数组固定在内存中来修复这个问题,但是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

只是为了确保我明白,在我的代码,该文件的内容超出范围时,函数结束,因为它是在'的byte []文件分配= File.ReadAllBytes(文件名);'?非常感谢答案! – Gilbrilthor 2013-02-10 01:39:07

+0

是的; 'file'是字节数组的唯一引用,所以当方法返回时,垃圾回收器可以随时回收内存。 (虽然你的'IntPtr'指向数组,它不是一个* managed *引用,所以垃圾回收器会忽略它。)但是GC可能不会立即运行,这就是为什么当只有一个文件的时候你逃脱了它。 – 2013-02-10 04:14:31