ファイルをバイナリ形式でオープンし任意の位置を読み書き

[戻る]
アーネット 2002/07/28(日) 06:14:32
(開発環境) Win98 VC++6.0 MFC
 みなさん初めまして。VC++をこの夏休みで習得しようと奮闘している者です。
参考書を購入し独学でやってきましたが、現実はなかなか厳しいようです・・・。

 機能設計の概略は、家庭用ゲーム機のセーブデータファイル(約700KB)を
バイナリ形式で開いて任意の位置を読み書きし
エディットボックスによって編集するというものです。
また必要があればチェックサムの計算も自動的に行うものを考えています。

 具体的な手順を説明させて頂きますと、CDocクラスのメンバ関数Serialize使って
ファイルからCViewクラスのメンバ変数m_men00、m_men01に格納します。
そしてm_men00、m_men01に格納された値をエディットボックスで編集し
それを元のファイルに書き込みたいのです。
ファイルからのデータを読み込みエディットボックスに表示させることはなんとか出来ました。
しかし、エディットボックスで編集した値を元のファイルに書き込む際に
つまづいてしまいました・・・。
何かご存知の方がいらっしゃれば、未熟者の私をご指導下さいませ。宜しくお願いします。


(data.xxx)
0x00000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
0x00010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
  :           :        :        :
0x00100  11 22 00 00 00 00 00 00  00 00 00 00 00 00 00 00
  :           :        :        :
0xabbe0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
0xabbf0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00

上記のファイルをオープンし、オフセット0x00100の0x11を0x33に
オフセット0x00101の0x22を44に、と下記のように編集したいのです。

(data.xxx)
0x00000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
0x00010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
  :           :        :        :
0x00100  33 44 00 00 00 00 00 00  00 00 00 00 00 00 00 00
  :           :        :        :
0xabbe0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
0xabbf0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00


 プログラムを実行しメニューバーやツールバーから 
「上書き保存」項目または「名前を付けて保存」項目を選択すると
「C:\VC\VC\dataのシークに失敗しました。」という警告メッセージが表示されてしまいます。
 恐らく「????????」の部分で既存のファイルをオープンする処理を
記述しなければならないと予測しているのですが・・・。


参考にして頂くためソースコードを記しておきます。

void CDoc::Serialize(CArchive& ar)
{
    // ビューのポインタを取得します。
    POSITION pos = GetFirstViewPosition();
    CView *pView = (CView*)GetNextView(pos);

    CFile *cf;
    cf = ar.GetFile();
        
    if (ar.IsStoring())
    {
        // TODO: この位置に保存用のコードを追加してください。

        // エディットボックスに入力された値を取得します。
        pView->UpdateData(TRUE);
        
        ????????

        // ファイルポインタをファイルの先頭から256バイト分だけ移動させる。
        LONG lOffset =  256, lActual;
        lActual = cf->Seek( lOffset, CFile::begin );

        // メンバ変数の値をファイルに書き込みます。
        ar << pView->m_mem_00;
        ar << pView->m_mem_01;
    }
    else
    {
        // TODO: この位置に読み込み用のコードを追加してください。

        // ファイルポインタをファイルの先頭から256バイト分だけ移動させる。
        LONG lOffset =  256, lActual;
        lActual = cf->Seek( lOffset, CFile::begin );

        // ファイルからメンバ変数に値を書き込みます。
        ar >> pView->m_mem_00;
        ar >> pView->m_mem_01;

        // エディットボックスに値を表示します。
        pView->UpdateData(FALSE);
    }
}

YuO [E-Mail] 2002/07/28(日) 07:08:40
cfの作成元を辿っていくと原因がわかります。

Serializeを呼び出すCDocument::OnSaveDocumentにおいて,
CArchiveに関連付けられているCFileオブジェクトが生成されていますが,
そのときにCFile::modeNoTruncateは指定されていません。

その為,Serializeが呼び出されるときにファイルは既に0byteに切り詰められてしまい,
Seekしようにもその位置が無くなっているのです。


というわけで,横着せずに全てを書き込む必要がありそうです。

YuO [E-Mail] 2002/07/28(日) 09:11:57
って,よく考えたらOnSaveDocumentって仮想関数でしたっけ。
CDoc::OnSaveDocumentを書けば少し横着できるかも。

でも,名前を付けて保存は一から書き込む必要がありますけどね。

アーネット 2002/07/28(日) 12:08:24
> その為,Serializeが呼び出されるときにファイルは既に0byteに切り詰められてしまい,
> Seekしようにもその位置が無くなっているのです。
詳しい解説ありがとうございます。確かに仰る通りでした。
保存用のコードでファイルポインタをシークする
>        //CFile::Seek の例
>        LONG lOffset =  -82688, lActual;
>        lActual = cf->Seek( lOffset, CFile::end );
の部分を削除したら「〜シークに失敗しました。」という警告メッセージが
表示されず無事に保存することができました。
ただ、2バイトのファイルになってしまいましたが・・・。


(data.xxx)
0x00000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
0x00010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
  :           :        :        :
0x00100  11 22 00 00 00 00 00 00  00 00 00 00 00 00 00 00
  :           :        :        :
0xabbe0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
0xabbf0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
↓        ↓        ↓        ↓       ↓ 
(data.xxx)
0x00000  33 44 
0x00010 


> って,よく考えたらOnSaveDocumentって仮想関数でしたっけ。
> CDoc::OnSaveDocumentを書けば少し横着できるかも。
このプログラムではCDocument::OnSaveDocumentを使用していませんでした(知りませんでし
た)。
AppWizardが自動生成したコードには
CDocument::OnNewDocumentが仮想関数として最初から定義されていました。
なのでCDocクラスにCDocument::OnSaveDocumentを仮想関数として追加しておきます。

MSDNライブラリで「CDocument::OnSaveDocument」をキーワードにして調べて見たところ
フレームワークがドキュメントを保存するときに特別な処理をさせる場合
この関数をオーバーライドするという事が分かりました。
[ファイル] メニューの [上書き保存] または [名前を付けて保存] コマンドの一部として
フレームワークがこの関数を呼び出したとき、予め元ファイルをオープンするのかもしれませ
ん。

PATIO 2002/07/29(月) 16:19:09
まず、ドキュメントクラスの使い方に問題があると思います。
横着というよりも、ドキュメントクラス=1ファイルという意識で行くべきでしょう。
ファイルの一部だけを読み込んでドキュメントとして使うのは望ましくありません。
なぜなら、名前を変えて保存という場合も考えられるからです。
名前を付けて保存というのは基本的に新しくファイルを起こすことになります。
したがって書き込むために必要な情報は全てドキュメントクラス内にあるのが基本でしょう。
素直にMFCのドキュメントビューアーキテクチャを使うのでしたらそうするべきですし、
ましてや初心者であるのならば、きちんと手順を踏んだ方法を使うべきです。
トリッキーな方法を知っておくのも財産ですが、基本的にな使い方や仕組みを理解する方が
先だと思いますよ。

あと、OnSaveDocumentは、フレームワークから呼び出されるのであってプログラマが
明示的に呼び出す必要はありません。MFCは、プログラマが変更した部分のみ変えれば、
プログラミングが出来るように作成されています。まあ、MFCも完全ではないので
全てにおいてというわけには行きませんが、基本スタンスはそれで良いと思います。

PATIO 2002/07/29(月) 16:31:25
MSDNライブラリで「CDocument::OnSaveDocument」をキーワードにして調べて見たところ
フレームワークがドキュメントを保存するときに特別な処理をさせる場合
この関数をオーバーライドするという事が分かりました。
[ファイル] メニューの [上書き保存] または [名前を付けて保存] コマンドの一部として
フレームワークがこの関数を呼び出したとき、予め元ファイルをオープンするのかもしれませ
ん。

そうではなくて、保存先のファイルをオープンしているのです。
上書き保存の場合は当然、読み込んだときと同じファイルです。名前を付けて保存の場合は
ダイアログで指定したファイルです。
基本的にドキュメントの中にはファイル丸ごとが入っているイメージで設計されているので
こういう実装になっているのです。
ですから、MFCの想定にしたがって素直に実装すれば特に問題になるようなことは無いはずで
す。

PATIO 2002/07/29(月) 16:32:27
すいません。
引用符号を付け忘れました。
上のブロックは引用です。

wood 2002/08/01(木) 06:23:24
PATIO さんのおっしゃるとおり
>Serialize
を使用している場合
>基本的にドキュメントの中にはファイル丸ごとが入っているイメージで設計
>されている
ようです

ですからファイルシークイメージで行いたいのであれば
CFileなどでファイルポインタを取得しSerializeを使わずに
処理するほうが良いかと思われます

古いスレッドには追加発言できません。