为什么位图中的数据超出了范围?

我为Skyrim创build了一个保存游戏pipe理器,并遇到了一个问题。 当我自己创buildSaveGame对象时,保存的位图部分正常工作。 然而,当我在一个循环中调用这个方法时,位图会带来一个错误的值,主要是一个与另一个保存游戏类似的值。

TL; DR – 为什么我的表单的列表框显示除了embedded图片之外的字符保存的正确信息? 而不是select正确的图片,它似乎select最后处理。 过程与通过打开的文件对话框select过程有何不同?


编辑:更新 – 我看着与每个SaveGame对象存储的位图,发现在scanDirectoryForSaves中创buildSaveGames时,不知怎的搞乱了它。 是否有位图的对象范围问题,并使用我不知道的字节指针?


这里是我的保存游戏对象的静态工厂的代码:

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

在我的表单中,我使用一种方法来读取某个目录中的所有保存文件,创buildSaveGame对象,并将它们存储在基于字符名称的字典中。

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

我将这些键添加到列表框中。 在列表框中select一个名称时,该表格上将显示该字符的最新保存。 每个字符的名称,date和其他字段都是正确的,但是位图是保存游戏图片的某个字符的变体。

我正在调用同样的方法来更新表单字段,从打开的文件对话框以及列表框中select一个保存。

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

使用带IntPtr的构造函数创建BitmapIntPtr必须指向内存块,该块在Bitmap对象的生存Bitmap保持有效。 您有责任确保内存块不被移动或释放。

但是,您的代码正在传递指向fileIntPtr ,这是一个托管的字节数组。 由于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; }