2013-02-09 12 views
5

Tôi đang tạo người quản lý trò chơi lưu cho Skyrim và đã gặp sự cố. Khi tôi tự tạo một đối tượng SaveGame, phần bitmap của lưu hoạt động chính xác. Tuy nhiên, khi tôi gọi phương thức đó trong một vòng lặp, bitmap mất một giá trị sai, chủ yếu là một giá trị tương tự như một trò chơi lưu khác.Tại sao dữ liệu trong bitmap bị lệch khỏi phạm vi?

TL; DR - Tại sao hộp danh sách biểu mẫu của tôi hiển thị thông tin chính xác để lưu ký tự trừ hình ảnh được nhúng? Thay vì chọn hình ảnh chính xác, nó xuất hiện để chọn xử lý cuối cùng. Quy trình khác với khi được chọn thông qua hộp thoại mở tệp như thế nào?


Chỉnh sửa: Cập nhật - Tôi nhìn vào các bitmap lưu trữ với từng đối tượng SaveGame và thấy rằng trong quá trình tạo các SaveGames trong scanDirectoryForSaves được bằng cách nào đó rối tung nó lên. Có một vấn đề phạm vi đối tượng với bitmap và sử dụng một con trỏ byte mà tôi không biết?


Đây là mã cho nhà máy tĩnh của tôi lưu trò chơi của đối tượng:

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

Trên mẫu của tôi, tôi sử dụng một phương pháp để đọc tất cả các file lưu trong một thư mục nào đó, tạo các đối tượng SaveGame ra khỏi và lưu trữ chúng trong từ điển dựa trên tên nhân vật.

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

Tôi thêm khóa vào hộp danh sách. Khi tên được chọn trên hộp danh sách, lần lưu mới nhất cho ký tự được hiển thị trên biểu mẫu. Tên, ngày tháng và các trường khác là chính xác cho mỗi ký tự, nhưng bitmap là một biến thể của một số ký tự nhất định lưu hình ảnh trò chơi.

Tôi đang gọi phương thức tương tự để cập nhật các trường biểu mẫu trong cả việc chọn lưu từ hộp thoại tệp mở cũng như hộp danh sách.

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

Trả lời

4

Khi bạn tạo một Bitmap sử dụng constructor mà phải mất một IntPtr, các IntPtr phải trỏ đến một khối bộ nhớ mà vẫn có giá trị cho các đời của đối tượng Bitmap. Bạn chịu trách nhiệm đảm bảo rằng khối bộ nhớ không được di chuyển hoặc deallocated.

Tuy nhiên, mã của bạn đang chuyển một số IntPtr trỏ đến file, là mảng byte được quản lý. Vì không có gì tham khảo file sau khi trả về ReadSaveGame, bộ thu gom rác sẽ tự do xác nhận lại bộ nhớ và sử dụng lại nó cho tệp tiếp theo. Kết quả: bitmap bị hỏng.

Mặc dù bạn có thể khắc phục sự cố này bằng cách ghim mảng trong bộ nhớ với GCHandle, có lẽ dễ dàng hơn và an toàn hơn để chỉ việc quản lý bộ nhớ của riêng mình Bitmap. Đầu tiên tạo ra một sản phẩm nào Bitmap, sau đó thiết lập các bit của nó:

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

Chỉ cần chắc chắn rằng tôi hiểu, trong mã của tôi, nội dung tập tin đi ra khỏi phạm vi khi hàm kết thúc bởi vì nó được giao nhiệm vụ trong 'byte [] file = File.ReadAllBytes (Filename); '? Cảm ơn rất nhiều vì câu trả lời! – Gilbrilthor

+0

Có; 'file' là tham chiếu duy nhất đến mảng byte, vì vậy khi phương thức trả về, bộ thu gom rác được phép lấy lại bộ nhớ bất cứ khi nào nó muốn. (Mặc dù 'IntPtr' trỏ tới mảng, nó không phải là tham chiếu * được quản lý *, vì vậy trình thu gom rác bỏ qua nó.) Nhưng GC có thể không chạy ngay lập tức, đó là lý do bạn bỏ đi khi chỉ có một tệp. –