DOBON.NET DOBON.NETプログラミング掲示板過去ログ

ディスクキャッシュを通さずにファイルを書き出すには?

環境/言語:[VB.NET 2002]
分類:[.NET]

どうしても自己解決できず、
こちらに書き込まさせていただきました。


FileStreamでファイルを書き出そうとした場合、
ディスクキャッシュを通さない方法は見つかりませんでした。
これができれば一番よいのですが・・・

最初はFileStream.Flushで書き出せそうな気もしたのですが、
あくまでバッファしていたものを直ちに書き出すというだけで、
そのうしろのディスクキャッシュまでは制御できないようです。

しかも、よくよく調べてみると、FileStream.Closeしたときにも
自動でFlushされているらしく、あまり意味がないようです。


そこで、ほかの方法を探したところ、WindowsのAPIで、
CreateFileで開き、WriteFileで書き出す際に、
CreateFileの時点で「FILE_FLAG_WRITE_THROUGH」を指定すると、
ディスクキャッシュを通さず書き出せるという情報を見つけました。

しかし私、APIに関しては初心者でして、(VBもそんなに自信は・・・)
たまにAPIを使ったとしても、どこかのサイトをコピペする程度です。


http://support.microsoft.com/kb/165942/ja
ここのように、CreateFileとWriteFileの使い方のサンプルは
いくつか見つけたのですが、どれもおそらくVB6用で、
VB.NET 2002ではどうすれば使えるのかまったくわかりません。

ためしにコピペ、エラーが出ている部分は適当に修正、実行してみましたが、
fSuccess = WriteFile(fHandle, anArray(LBound(anArray)), _
BytesToWrite, lBytesWritten, 0)
この部分で、
「オブジェクト参照がオブジェクトインスタンスに設定されていません。」
と出てしまい、ハンドルは指定してありますし、エラーの原因が不明です。


どなたかご教授お願いできますでしょうか。
多少贅沢かもしれませんが、VB.NET 2002用へ修正後のソースなども
一緒にご提示いただけたりすると、とてもうれしいです。
よろしくお願いします。
> FileStreamでファイルを書き出そうとした場合、
> ディスクキャッシュを通さない方法は見つかりませんでした。
なぜ、ディスクキャッシュを通さない方法を求めるのですか?

> この部分で、
> 「オブジェクト参照がオブジェクトインスタンスに設定されていません。」
> と出てしまい、ハンドルは指定してありますし、エラーの原因が不明です。
その書き換えた結果のソースコードを提示して下さい。
「このように書き換えたのでは?」と想像することもできますが、思わぬ落とし穴があったりして、質問者と回答者の間で無用なやりとりが行われる可能性があります。

> 多少贅沢かもしれませんが、VB.NET 2002用へ修正後のソースなども
> 一緒にご提示いただけたりすると、とてもうれしいです。
私見ですが、甘すぎます。
ここで正解と言えるコードが示されても、ソースに貼り付けるだけ、つまり、自分で理解しようとしないとなると力がつきませんし、Win32APIの度に質問するハメになります。

趣味であれ、業務であれ、自分で理解・説明できないコードを書かないで下さい。
それは後でバグ等の形で出てきますし、業務であれば他の人の作業時間を奪う等の損害になります。
返信ありがとうございます。

> なぜ、ディスクキャッシュを通さない方法を求めるのですか?

書き出し直後にディスクダンプを行ったりしたい、
突然の停電時に備えた一応の対策にもなる、などの理由です。
NTFSの遅延書き込みについては半分あきらめていますのでほぼ無視して良いのですが、
ディスクキャッシュだけはどうにかしたいといった状況です。


> その書き換えた結果のソースコードを提示して下さい。
> 「このように書き換えたのでは?」と想像することもできますが、思わぬ落とし穴があったりして、質問者と回答者の間で無用なやりとりが行われる可能性があります。

たしかにそうですよね。長文になってしまいそうなので控えていたのですが、
やはりそのような問題があると思いますので、提示します。
少し長くなってしまいますが、よろしくお願いします。


まず、http://support.microsoft.com/kb/165942/ja
のサンプルをそのまま貼り付けると、以下12ヶ所にエラーが出ます。

(1) Type MyType  ←'Type'ステートメントは現在サポートされていません。
(2) value As Integer  ←宣言が必要です。
(3) End Type  ←'End'ステートメントが有効ではありません。

(4) Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, _
ByVal lpBuffer As Any, ・・・
   ↑'As Any'は、'Declare'ステートメントではサポートされていません。

(5) Private Declare Function WriteFile Lib "kernel32" ( _
ByVal hFile As Long, ByVal lpBuffer As Any,
   ↑'As Any'は、'Declare'ステートメントではサポートされていません。

(6) Sub fillArray(ByVal anArray() As MyType)  
   ↑型'MyType'が定義されていません。

(7) Dim T(1000) As MyType  ←型'MyType'が定義されていません。
(8) Dim S(1000) As MyType  ←型'MyType'が定義されていません。

(9) Sub readArray(ByVal Fname As String, ByVal anArray() As MyType)
   ↑型'MyType'が定義されていません。

(10) BytesToRead = (UBound(anArray) + 1) * LenB(anArray(0))
   ↑名前'LenB'は宣言されていません。

(11) Sub writearray(ByVal Fname As String, ByVal anArray() As MyType)
   ↑型'MyType'が定義されていません。

(12) BytesToWrite = (UBound(anArray) + 1) * LenB(anArray(0))
   ↑名前'LenB'は宣言されていません。


そこで、自己知識を頼りに、(1)〜(3)を以下のように書き換えてみました。
 Structure MyType
  Dim value As Integer
 End Structure
これで、MyTypeの定義はできた(と思う)ので、
(6)(7)(8)(9)(11)のエラーも消え、残るエラーは(4)(5)(10)(12)となりました。

(4)(5)の「As Any」は、.NETでは使えないとのことらしいので、今回は
「Any」には「MyType」が当てはまると考え、「As MyType」へ書き換えました。

(10)(12)の「LenB」も、.NETでは使えないとのことらしいので、
回避策はいろいろあるらしいのですが、今回はMyTypeがIntegerなので、
「Len」でも特に問題はないと考え、「Len」へ書き換えました。

これで全てのエラーは消え、プロジェクトのスタートアップも
「Form1」から「Sub Main」へ変更し、ビルド、実行したのですが、
 Sub writearray(ByVal Fname As String, ByVal anArray() As MyType)
の中の、
 fSuccess = WriteFile(fHandle, anArray(LBound(anArray)), _
BytesToWrite, lBytesWritten, 0)
の部分で、
「オブジェクト参照がオブジェクトインスタンスに設定されていません。」
とエラーが出て止まってしまい、原因が分からず、行き詰っている状況です。


> 私見ですが、甘すぎます。
> ここで正解と言えるコードが示されても、ソースに貼り付けるだけ、つまり、自分で理解しようとしないとなると力がつきませんし、Win32APIの度に質問するハメになります。

誤解を与えてしまったようで申し訳ありません。
よく、質問をすると、高レベルな回答をしてくださる方がいらっしゃるのですが、
高レベルすぎて私には意味が理解できないことが多いので、解説だけではなく、
実際にどのようになるのかも提示して頂けると、理解しやすいと思いました。
決して、理解する気がない、答えだけほしいといった安易な考えではありません。


という訳で、上記のような状況なのですが、
改めて、ご教授よろしくお願いします。
2008/05/10(Sat) 01:45:14 編集(投稿者)

今回、コードを実際には修正していません。
#明日も仕事なのでご勘弁を。

> そこで、自己知識を頼りに、(1)〜(3)を以下のように書き換えてみました。
>  Structure MyType
>   Dim value As Integer
>  End Structure
既に修正ミスが起きています。

VB6
Integer = 2バイト
Long = 4バイト

VB.NET
Short = 2バイト
Integer = 4バイト
Long = 8バイト

> (4) Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, _
> (5) Private Declare Function WriteFile Lib "kernel32" (ByVal hFile As Long,
hFileはIntPtr型であるべきです。

これだけでは直らないかもしれませんが、ちょっと私の時間切れで申し訳ない。

> 「オブジェクト参照がオブジェクトインスタンスに設定されていません。」
> とエラーが出て止まってしまい、原因が分からず、行き詰っている状況です。
この例外が出て止まったときに、それぞれの変数にカーソルを合わせて、期待したとおりの数値が出ているか確認すると良いかもしれません。
また、Nothingとか出ているようなパラメータがあれば、それが怪しいので確認してみて下さい。

> 決して、理解する気がない、答えだけほしいといった安易な考えではありません。
了解しました。
今回のようにどのように直したと書いて頂ければ、突っ込みしていきますので、分かりづらいようなことがあればご指摘下さい。
APIの宣言をC#やVB.NETで使用する場合には次のサイトが参考になります。
http://pinvoke.net/

今回の問題はAs LongとByRefですね。
先にも書きましたが、VB6とVB.NETでは整数型のサイズが変化しています。

今回のDelclare文で次の点で注意すべきです。

・整数型を正しいサイズのものを選択する。
(今回の場合、As Longとなっているところはほとんど、As Integerで書かないといけない)
・hFile, lpOverlappedはポインタを表すので、As IntPtrとする。
・API側で書き換えるもの、すなわちlpBuffer、nNumberOfBytesWritten、nNumberOfBytesRead等はByRefとする。

概ねそんなところだと思います。
コンパイルが通る = 問題ないということはあり得ないので、Declare文を書く際は注意深く確認する必要があります。
ご指摘いただいた内容をもとに、あれからいろいろと試行錯誤し、やっとできました。


サンプルそのままだとわかりにくいので、
readの部分は消してwriteだけにしてみたり、MyTypeも構造体ではなくしたりと、
できるだけシンプルにすることで、理解しやすくなりました。

途中でわからなくなって何度も最初からやり直したりしたので、
結局どこが間違っていたのかははっきりとはわからなくなってしまいましたが、
やはり、ハンドルはIntPtr型にすること、APIが書き換える部分を中心に
ByValなのかByRefなのかが正しくないことなどが原因だったように思います。


やはり、APIを使うと、API側で何かエラーがあっても見えないので、
間違いに気づきにくいですね・・・
VBの外部のことなので仕方のないことだとは思いますが・・・

Createした段階でアクセス権の指定などが正しくなかったり、
単純にAPI宣言と実際の変数の型が少しだけ異なっていたりするなどが原因で、
何のエラーも出ず実行できるのに、なぜかWriteやCloseができていなかったりして、
そこでかなり時間をとられました。


また、そもそもの目的だった、ディスクキャッシュを通さない方法も、
CreateFileのフラグの指定で実現できました。
実現できたことは、Close前のFlushFileBuffersを意図的に無効化し、
書き出し直後のダンプを比較することで確認できました。


Azuleanさんのご指摘内容はとても的確でした!
半分あきらめかけていたのでとてもうれしいです!
本当にありがとうございました。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板