5

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

1 に答える 1

4

を受け取るコンストラクタBitmap使用してを作成する場合、 は、オブジェクトの存続期間中有効なメモリ ブロックを指している必要があります。メモリのブロックが移動または割り当て解除されないようにする責任があります。IntPtrIntPtrBitmap

ただし、コードは、マネージ バイト配列であるIntPtrを指す を渡しています。返された後はfile何も参照されないため、ガベージ コレクターは自由にメモリを再利用し、次のファイルに再利用できます。その結果、ビットマップが破損します。fileReadSaveGame

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;
}
于 2013-02-10T00:10:43.917 に答える