トップ回答者
名前をつけて保存、上書き保存の定石

質問
-
1つのファイルを開き、編集し、保存するアプリケーションを作成しています。
- やりたいこと:
- ファイルを共有モード(FILE_SHARE_READ)で開き、保存が成功したら閉じて保存先を開き直す。保存が失敗したら元のファイルを開き続ける。
- 問題点:
- 上書き保存の場合(名前を付けて保存で同名ファイルを指定)に、自身がそのファイルをFILE_SHARE_READで開いているために、書き込みができない。
保存先と同一フォルダに一時ファイルを作成してデータを書き出し、書き出しが成功したら、::ReplaceFile(保存先, 一時ファイル, 0, NULL, NULL) にてリネームを行うが、上書き保存の場合は(保存先を開いているので)このAPIが失敗します。
この問題を避けるために::ReplaceFile()を呼び出す直前に必ずファイルを閉じ、::RelpaceFile()が失敗したら開き直して誤魔化しているのですが、上書き保存時以外は必要のない処理なので、上書き保存か否かを判定する方法が知りたいです。フルパス名の比較では、仮想ドライブやUNCパスなど同じファイルを別の名前で指す場合にうまくいかないと思います。 - 知りたいこと:
- 1.安全な上書き保存を行うための定石
2.同一のファイルか否かを判定する方法(フルパス名比較ではない方法)
回答
-
まず前提条件を明確にしなければなりません。「読み込み共有」をしている
ということから考えると、1.対象ファイルは他のアプリケーションが任意のタイミングで参照する。
2.対象ファイルは一時的であっても、全内容が無くなるか、ファイル自体が
なくなる瞬間があってはならない。上記条件を満たした上で、
3.対象ファイルの内容の一部を書き換えなければならない。
ということでしょうか(やや自信なし)。
この場合はHFILEは保存的に使用せざるをえません。つまり、
唯一のHFILEの実体に対して変更する必要があるため、別名ファイルに
一時保管してから名前を変えるという手法は使えないと考えられます。
従ってファイルをオープンするときの共有オプションやアクセスタイミングを
他のアプリケーションと調整する必要があると思われます。
このファイルにアクセスする全てのアプリケーションはファイルの部分のみを
書き換えなければならず、全体のレイアウト変更を伴うものは、
一般的には許されないと考えられます。さて、ファイル全体の「上書き保存」という意味は、通常、以下の手順となります。
4.前提として、対象ファイルの変更済みの全ての内容がテンポラリファイルなどの
他のファイルか、又はメモリー上に存在している。これをソースとする。
5.対象ファイルを開くときにファイルサイズを0にしてオープンする。
この瞬間、ファイルは一時的に全ての内容を失う。
6.ソースから対象ファイルに、全ての内容を書き込んで、閉じる。
7.この(5~6)間、他のアプリは一時的に当該ファイルにアクセスできない。
8.番号7.が成立するので一般には、この間は一切の共有は行わないのが普通。2.の条件があると、5.の手順に抵触するので、この方法は使えないわけですね。
5.でサイズを0にしない場合であっても、全体を保存せざるを得ないので
他のアプリケーションから見た場合の意味は同じになります。
つまり、当該アプリケーションが書き換え中は、他のアプリケーションから、
一切のアクセスはできないということになります。これをむりやり共有する場合は、ファイル内に当該ファイル内に
当該ファイルの内容を参照可能であるかを示す、何らかのフラグ等を導入する必要が
あるかもしれません。ただし、これは、当該ファイルを共有無しでオープンできるか
テストするのと手間は大差ないかもしれません。最後に、ご指摘の通り、
プログラムから見た場合、二つの異なる物理的なパスが、唯一の物理的ファイルを
指す場合がありえます。従って、二つの異なるフルパスが別のファイルである
保障はありません。そのファイルにどのようなアクセスが可能であるかは、
設計上明らかになっている場合を除けば、実際にやってみるまでわかりません。
すべての返信
-
BlueSkyColorsさんへ:
アプリケーション利用者が「名前を付けて保存」を実行し、そこで指定したファイル名が現在のファイル名と同一だった場合への対応を検討されているのだと読みました。ユーザー操作なら論理的に矛盾した操作もあり得るし、それを否定するわけにはいかないでしょう。質問者さんへ:
名前を付けて保存ダイアログにはOFN_OVERWRITEPROMPTの指定ができます。これで利用者に対して上書きの許可を得ることができます。
上書きの許可が得られているならロジックは簡単になるのではないでしょうか。名前を付けて保存でファイル名を選択させる
if( 選択された、キャンセルされなかった ){
テンポラリファイルに保存し、閉じる
if( 選択されたファイル名のファイルが存在する ){
// 上書き許可を得ているので
選択されたファイル名のファイルを削除する
}
テンポラリファイルから選択されたファイル名へリネーム
}で済みませんか?
- 回答の候補に設定 山本春海 2012年8月13日 8:26
-
まず前提条件を明確にしなければなりません。「読み込み共有」をしている
ということから考えると、1.対象ファイルは他のアプリケーションが任意のタイミングで参照する。
2.対象ファイルは一時的であっても、全内容が無くなるか、ファイル自体が
なくなる瞬間があってはならない。上記条件を満たした上で、
3.対象ファイルの内容の一部を書き換えなければならない。
ということでしょうか(やや自信なし)。
この場合はHFILEは保存的に使用せざるをえません。つまり、
唯一のHFILEの実体に対して変更する必要があるため、別名ファイルに
一時保管してから名前を変えるという手法は使えないと考えられます。
従ってファイルをオープンするときの共有オプションやアクセスタイミングを
他のアプリケーションと調整する必要があると思われます。
このファイルにアクセスする全てのアプリケーションはファイルの部分のみを
書き換えなければならず、全体のレイアウト変更を伴うものは、
一般的には許されないと考えられます。さて、ファイル全体の「上書き保存」という意味は、通常、以下の手順となります。
4.前提として、対象ファイルの変更済みの全ての内容がテンポラリファイルなどの
他のファイルか、又はメモリー上に存在している。これをソースとする。
5.対象ファイルを開くときにファイルサイズを0にしてオープンする。
この瞬間、ファイルは一時的に全ての内容を失う。
6.ソースから対象ファイルに、全ての内容を書き込んで、閉じる。
7.この(5~6)間、他のアプリは一時的に当該ファイルにアクセスできない。
8.番号7.が成立するので一般には、この間は一切の共有は行わないのが普通。2.の条件があると、5.の手順に抵触するので、この方法は使えないわけですね。
5.でサイズを0にしない場合であっても、全体を保存せざるを得ないので
他のアプリケーションから見た場合の意味は同じになります。
つまり、当該アプリケーションが書き換え中は、他のアプリケーションから、
一切のアクセスはできないということになります。これをむりやり共有する場合は、ファイル内に当該ファイル内に
当該ファイルの内容を参照可能であるかを示す、何らかのフラグ等を導入する必要が
あるかもしれません。ただし、これは、当該ファイルを共有無しでオープンできるか
テストするのと手間は大差ないかもしれません。最後に、ご指摘の通り、
プログラムから見た場合、二つの異なる物理的なパスが、唯一の物理的ファイルを
指す場合がありえます。従って、二つの異なるフルパスが別のファイルである
保障はありません。そのファイルにどのようなアクセスが可能であるかは、
設計上明らかになっている場合を除けば、実際にやってみるまでわかりません。