我为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
的构造函数创建Bitmap
, IntPtr
必须指向内存块,该块在Bitmap
对象的生存Bitmap
保持有效。 您有责任确保内存块不被移动或释放。
但是,您的代码正在传递指向file
的IntPtr
,这是一个托管的字节数组。 由于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; }