我正在为Skyrim创建一个保存游戏管理器并遇到了一个问题。当我自己创建一个SaveGame对象时,保存的位图部分正常工作。但是,当我在循环中调用该方法时,位图会出现错误的值,主要是与另一个保存游戏类似的值。
TL; DR - 为什么我的表单的列表框显示正确的字符保存信息,除了嵌入的图片?它不是选择正确的图片,而是选择上次处理的图片。该过程与通过打开文件对话框选择的过程有何不同?
编辑:更新 - 我查看了存储在每个SaveGame对象中的位图,发现在scanDirectoryForSaves中创建SaveGames期间会以某种方式弄乱它。是否存在使用位图的对象范围问题并使用我不知道的字节指针?
以下是我的保存游戏对象的静态工厂的代码:
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);
}
在我的表单上,我使用一种方法来读取某个目录中的所有保存文件,从中创建SaveGame对象,并根据字符名称将它们存储在字典中。
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();
}
答案 0 :(得分:4)
当您使用Bitmap
IntPtr
创建IntPtr
时,Bitmap
必须指向在{的生命周期内保持有效的内存块{1}}对象。您有责任确保不会移动或取消分配内存块。
但是,您的代码传递的IntPtr
指向file
,这是一个托管字节数组。因为file
返回后没有引用ReadSaveGame
,所以垃圾回收器可以自由回收内存并将其重用于下一个文件。结果:损坏的位图。
虽然您可以通过使用constructor将数组固定在内存中来解决此问题,但让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;
}