InputB関数でエラーが出ます

解決


EG  2010-01-26 20:12:03  No: 102183  IP: [192.*.*.*]

VB5、WinXP

質問させていたできます。

下の自作関数を使っていると、
「ファイルにこれ以上データがありません。(Error 62)」
というエラーが出ました。
今までずっと使っていたのですが一度もこのようなエラーが出たことはなかったのに突然出ました。
読み込もうとしているテキストファイルの内容は普通のテキストしか入っていないのですが。

このようなエラーが出る原因としてどのようなことが考えられるでしょうか?

どうかよろしくお願いいたします。

Public Function ReadTextFile(FileName As String) As String

    Dim s As String
    Dim MySize As Long
    Dim f As Integer
    
    MySize = FileLen(FileName)
    f = FreeFile
    Open FileName For Input As #f
        s = InputB(MySize, #f)
    Close #f

    ReadTextFile = StrConv(s, vbUnicode)
    
End Function

編集 削除
なな  2010-01-29 08:39:10  No: 102184  IP: [192.*.*.*]

実は解決法は思い付かなかったのですが・・・

今もずっとそのエラーは出るのでしょうか。
それとも一度出ただけで、それ以降はちゃんと動作しているのでしょうか。
(そもそも今まで大丈夫だったっていうそのコードの「実績」期間はどのくらいなんでしょう)

InputB関数って(Inputステートメントもですが)ほぼ使ったことがないので挙動を熟知してませんが、
そのエラーの発生理由自体は表示の通りです。
ファイルの終端を越えてアクセスさせようとした事が原因のはずです。

アクセスするファイルはどんなファイルなんでしょうか。(多分単なるテキストファイルだとは思いますが)

テキストだったとして、その中身の文字列は日本語ですか?
それとも半角のASCII文字ですか?あるいはその混載でしょうか?
おそらくエラーが出ていなかった頃とはそのへんが異なっているてそれが要因になってるんじゃないでしょうか?

とはいえ、それがどう解決に繋がってくるのかまではわからないのですけれど。
Input関数ではなくちゃんとInputB関数使ってますし、コード的に問題はないように思うのですよね。

ということで他の原因の候補を。
FileLenでサイズを確認した後〜実際にファイルを読み込むまでの間に「外側で」ファイルに何かが起こったんじゃないかしら。

例えばアンチウイルスソフトやファイル共有ソフトが一瞬そのファイルをロックしたとか、ほかの「誰か」がその瞬間中身を書き換えたからサイズが不正だったとか、ファイルの権限などのファイルシステムによる制約のせいだとか、物理的なHDDの故障とかデータ破損だとか。
んー、外的要因はこんなもんでしょうか。

'今更ですが、私はVB6ユーザーなのでVB5についての固有の挙動は申し訳ないですが存じません。

編集 削除
GOD  2010-01-29 10:42:59  No: 102185  IP: [192.*.*.*]

> このようなエラーが出る原因としてどのようなことが考えられるでしょうか?
>
^Z が含まれていたりしませんか?
テキストファイルの文字コードは?(UTF-16 で漢字を使用すると^Zが含まれるみたいですが。)

編集 削除
EG  2010-01-29 17:47:23  No: 102186  IP: [192.*.*.*]

ななさん、GODさん、ご回答ありがとうございます。

> 今もずっとそのエラーは出るのでしょうか。
はい、このファイルを読み込もうとすると何回やっても出ます。

> (そもそも今まで大丈夫だったっていうそのコードの「実績」期間はどのくらいなんでしょう)
よく使うのでおそらく何千回以上だと思います。

> アクセスするファイルはどんなファイルなんでしょうか。
単なる日本語の文字列です。IE(Internet Explorer)画面上の選択テキストを保存したものです(多分)。拡張子は「txt」です。

また、このファイルを別のフォルダに移動しても同じエラーが出ます。

MySize = FileLen(FileName)
この行でMySizeは「42」という値になりました。エクスプローラ上でこのファイルのアイコンを右クリックして「プロパティ」をクリックすると現れるプロパティウインドウには、サイズが同じく「42」と書いてありました。

> ^Z が含まれていたりしませんか?
すいません。「^Z」とはどういう意味でしょうか?
そのファイルの内容は全角文字(漢字やひらがな)20個と「;」が一個と末尾に半角の空白が1つだけです。

> テキストファイルの文字コードは?
よく分からないのですが、普通の文字コードだと思います。メモ帳で開けています。秀丸エディタで開くと「日本語(Shift-JIS)」となっていました。

このテキストファイルをメモ帳で開いてメニューの「すべて選択」をクリックして、そして「コピー」をクリックして、次に「貼り付け」をクリックして「上書き保存」をクリックしてメモ帳を閉じて、再度試すと、今度はエラーが出なくなります。

そのファイル自体をコピーしたもので試すとエラーが出ます。


長々とした文章でで申し訳ないのですが、
よろしくお願いいたします。

編集 削除
なな  2010-01-29 20:19:30  No: 102187  IP: [192.*.*.*]

話を聞く限り、そのファイルのほうに問題があるようです。
そのファイルのデータ中に何か問題のある箇所があるんじゃないでしょうか。
「コピー&ペーストすると問題なくなる」事から、不正な文字が含まれている可能性が高いです。
また、そうして作った問題ないテキストファイルのほうは、ファイルサイズが小さくなっていませんか?

一度、バイナリエディタあたりで問題のファイルを開いてみて何が問題なのか検証する事をお勧めします。
(で、問題がわかったらレスを書いていただけると後に誰かが助かります)


なお、VB6/VBA/VBSが標準で扱える文字コードは
「Shift JIS」(=シフトJIS、S-JISとも)と、
「Unicode」(この場合指すのはUTF-16)です。

そのファイルのテキストに、これ以外のものが含まれているとファイル入出力系の関数やステートメントで問題が発生する可能性はあります。
でも、今回の場合Input"B"ですし・・・文字としてではなく、単なる2進数の塊(バイナリ)として扱っているはずなので問題にはなりそうにないのですが。


私はコンソール系には疎いのでEGさんの言う「^Z」というのがどれにあたるのかは存じませんが、これは文字コード中に存在する「制御文字コード」(文字以外の見えないコード)の一種だろうという事はわかります。
文字以外を示すコードなので表示はされず目には見えないものです。
(目に見える身近な例では、タブ、改行などが目視できる制御コードの一例です)



なお、文字コードとは文字をどういう風に保存して扱うかというルールです。
「第一水準漢字」という言葉を聞いた事はありませんか?
(私はPCに触れる前の子供の頃に一般的に聞いたことがありました)
この第一水準、第二水準という漢字の選定は、まさに文字コードを作るために行われたものです。
一般的な漢字の一覧表を作り、その文字一つ一つに順番を決め、それらを円滑に符号化する手順が規格化されました。
文字コードは、文字への番号の振り方やその文字の表現方法を規定した規格の事です。
例えばShiftJISでは「あ」を「82A0」という番号で表します。テキストファイルにはこの「82A0」が保存されています。
ですが、JISコードでは「あ」は「2422」という番号で表します。逆に「82A0」はJISコードでは存在しない文字なので勘違いして化けるか無視されるかエラーが発生します。


なおテキストファイルには「どの文字コードで保存されているか」という情報は全く含まれていませんので、メモ帳がShiftJISだと言ったとしても100%鵜呑みには出来ません。
テキストファイルの内部には文字番号の羅列だけが存在します。
各種テキストエディタは文字を表示するのがお仕事ですので、開かれたファイルが「例えどの文字コードとして読んでも正しくない」ものだったとしてもいずれかの文字コードとしてどうにか表示する必要があります。
「ShiftJISとしては正しくない番号が含まれてるけど、他の文字コードでも読めないからShiftJISとして表示」しただけなのかもしれません。


日本語を表す文字コードだけ見ても「ShiftJIS」「EUC」「JIS(旧JIS)」「Unicode」など多数あります。(さらにUnicodeには大量に別形式が存在します)
1つに統合されていれば誰も迷うことはないのですが、歴史上と実用上の問題で複数のルールが同時多発的に一般化しそのまま現在も統一できずむしろ増える一方なのが現状です。
日本語だけの問題ではなく実際に他国でも同じような状況にあり、世界的に見ると多種多様な文字コードが多数存在する状態になっています。


「文字コード」の詳細については以下あたりを参照してください。
http://euc.jp/i18n/charcode.ja.html
http://ja.wikipedia.org/wiki/%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89
http://www.kanzaki.com/docs/jcode.html

編集 削除
なな  2010-01-29 20:21:12  No: 102188  IP: [192.*.*.*]

前述の「EGさんの言う〜」は正しくは「GODさんの〜」でした。
GODさんごめんなさい!

編集 削除
EG  2010-01-29 22:58:19  No: 102189  IP: [192.*.*.*]

ななさん、ご回答ありがとうございます。

文字コードについての詳しい解説をありがとうございます。
ご紹介頂いたサイトをよく読んでみたいと思います。

なんとか原因が分かりました。

Open FileName For Input As #f・・・①
↑この行を、
Open FileName For Binary Access Read As #f・・・②
↑に変えたらエラーが出なくなりました。

読み込めたので、一文字ずつAsc関数で調べたら、末尾の半角の空白だと思っていた文字はヌル(Chr(0))でした。(GODさんの言われていた「^Z」とは多分ヌルのことだったんでしょうね。)

また、
s = InputB(MySize - 1, #f)
このようにサイズを1つ減らしてやってもできました。

結局いろいろ考えた結果、InputB関数が「ヌル文字を飛ばして読み込む」という性質をもっているということが原因のようです。(本当かどうか分かりませんが)

また、InputB関数は「For Input」でオープンするとヌル文字を飛ばして読み込み、「For Binary Access Read」でオープンするとヌル文字を飛ばさずに読み込むようです。だから「For Input」でオープンするときは、InputB関数の引数に、読み飛ばしたヌル文字の数だけ差し引いたサイズを与えてやらないといけないみたいです。

もうこの自作関数を使うこと自体をやめようかと思っております。
ななさんや他の皆さんはテキストファイルを文字列変数に読み込みたいときどのようにされていますか?
(外部コンポーネントを使わない方法で)安全で確実で高速な方法を教えて頂ければと思っております。

よろしくお願いいたします。

編集 削除
GOD  2010-01-29 23:20:57  No: 102190  IP: [192.*.*.*]

^Z (Ctrl + Z)はテキストファイルの終端です。
http://questionbox.jp.msn.com/qa1438514.html

因みに ^A(Ctrl+A)は文字コード的に01H, ^Bは02H...アルファベットは26
文字なので ^Zは1AHです。

一つサンプルを作ってみたので動かしてください。ボタン1でファイルを作
った後、ボタン2で EG さんが作成した関数を呼び出します。そうすると同
じエラーが確認できます。
その後、読み出し部を「***** InputB テスト」で示しているコードに変更
すれば何バイト目でエラーが発生するか確認できます。
ファイル名は適当に変更してもらえればOKです。

---- サンプル
Private Const FILEPATH = "c:\hoge.txt"

Private Sub Command1_Click()
    'ファイル作成
    Dim buf(8) As Byte
    Dim FNo As Integer

    buf(0) = &H41   'A
    buf(1) = &H42   'B
    buf(2) = &H43   'C
    buf(3) = &H44   'D
    buf(4) = &H1A   '^Z(Ctrl+Z)
    buf(5) = &H31   '1
    buf(6) = &H32   '2
    buf(7) = &H33   '3
    buf(8) = &H34   '4
    FNo = FreeFile
    Open FILEPATH For Binary As FNo
    Put FNo, , buf
    Close FNo
End Sub

Private Sub Command2_Click()
    Call ReadTextFile(FILEPATH)
End Sub

***** InputB テスト
    Dim i As Long
    For i = 0 To MySize
        s = InputB(1, #f)
    Next

編集 削除
GOD  2010-01-29 23:36:00  No: 102191  IP: [192.*.*.*]

誤  For i = 0 To MySize
正  For i = 0 To MySize - 1

---
InputBは たしかにヌル文字は飛ばして読むかも。
私は、読み込み時は Binary モードしか使用しません。全てのコードが取得
できるので特に変えようと思ったことがないので。

編集 削除
なな  2010-01-30 00:14:59  No: 102192  IP: [192.*.*.*]

そういえばFor Inputって「テキストモード」でしたね・・・見落としてました。
オープンモードによって対象が変化した結果だったわけですか。
原因がわかって良かったですね。

>ななさんや他の皆さんはテキストファイルを文字列変数に読み込みたいときどのようにされていますか?
私はいつも相手がテキストであろうとバイナリであろうとかまわずFor Binaryで開いてますよ。
すべてを一気にGetステートメントで取り込んだ後オンメモリでUnicodeに変換処理してます。
(読み込むファイルが数百MBを超えるような場合は分割処理しますけど)

標準でのファイル入出力と言うとこのOpenステートメントか、あるいはFSOオブジェクトですが、
VB6の時点のFSOはテキストモードしか扱えない不完全体だった気がします。
結局、VB6では嫌でもOpenステートメントを使わざるを得ないんじゃないでしょうか。


OpenステートメントはVB6のコーディング規則を逸脱する特殊な記述法が多くて私も嫌いです。
細かい情報を得るのが難しくて、クラス・オブジェクトを理解するよりよっぽど難解だと思うのは私だけでしょうか。
(テキストモードでPrintステートメントが最後に改行を出力してしまうのを防ぐ方法なんてコードの最後に半角スペースとセミコロンを記述するという規則でしたが、あれはいまだに理解し難いよ)

というかいつも自作関数で処理してるので、もうOpenの仕様とかうろ覚えになってる私です。
とりあえず、私が普段使ってる関数を公開しておきますね。
一応ファイルの一部分だけ読み出す事に対応したり、
引数のチェックと自動補正機能があったりしてコードが長ったらしくなってますけど。


Public Function FileByteRead(ByVal FilePath As String, ByRef Bytes() As Byte, Optional StartPoint As Long = 0, Optional ByteLength As Long = -1) As Long
'||  正式関数名  :バイナリリード  Ver1.2 (c)2004/04/12 なな. - 2007/02/11改定 なな.
'||  動作        :ファイルを開いてバイナリ読み込み。
'||  引数        :FilePath:開くファイルのフルパス Bytes:読み込んだバイナリの格納用動的配列
'||              :StartPoint:読み込み位置の指定(0=先頭)  ByteLen:読み込みバイト数の指定(-1以下=ファイル終端まで)
'||  戻り値      :0以上=成功(バイト数)  -1=Open失敗 -2=読込みErr  -3=引数Err(SP)  -4=引数Err(BL)
'||  参照設定    :(なし)
'||  使用関数    :(なし)

Dim FileNo As Integer
Dim Ret As Long

'引数のチェック1(開始位置が0以下なら不正)
If StartPoint < 0 Then
    FileByteRead = -3
    Exit Function
End If

FileNo = FreeFile
On Error GoTo OpenErr '(ファイルが開けなければ不正)
Open FilePath For Binary As #FileNo 'バイナリアクセスモード
On Error GoTo 0

    'ファイルのチェック(ファイルサイズが0バイトの場合
    If LOF(FileNo) = 0 Then
        Close #FileNo
        FileByteRead = 0
        Erase Bytes  'メモリサイズ消去。
        Exit Function
    End If

    '引数のチェック2(開始位置がファイルの長さを超えているなら不正)
    If StartPoint > (LOF(FileNo) - 1) Then
        Ret = -3
    Else
        '引数の初期化A
        If ByteLength <= -1 Then
            ByteLength = (LOF(FileNo)) - StartPoint
        End If
        
        '引数のチェック3 (読み込み長が0以下なら不正)
        If ByteLength <= 0 Then
            Ret = -4
        Else
            '引数の初期化B(読み込みの長さ指定がファイルを越えていたら自動的にEOFまで)
            If StartPoint + (ByteLength - 1) > (LOF(FileNo) - 1) Then
                ByteLength = (LOF(FileNo) - 1) - StartPoint + 1
            End If
            
            On Error GoTo SizeErr
            ReDim Bytes(0 To (ByteLength - 1))   'メモリサイズの初期化
            On Error GoTo 0
          
            Get #FileNo, StartPoint + 1, Bytes    'ファイルから読み込み
        End If
    End If
Close #FileNo

FileByteRead = ByteLength

Exit Function
'----------------------------------------------------------------------
OpenErr:
    FileByteRead = -1
    'MsgBox "既知の例外エラー、ファイルオープンエラーが発生。"
Exit Function
'----------------------------------------------------------------------
SizeErr:
'ファイル開けたけど、読み込みバイト数が0以下になった場合発生。
    'とにかくファイルをクローズ。
    Close #FileNo
    
    FileByteRead = -2
    Erase Bytes  'メモリサイズ消去。


End Function

編集 削除
なな  2010-01-30 00:48:21  No: 102193  IP: [192.*.*.*]

横レスですいません。

> ^Z (Ctrl + Z)はテキストファイルの終端です。
http://questionbox.jp.msn.com/qa1438514.html
> 因みに ^A(Ctrl+A)は文字コード的に01H, ^Bは02H...アルファベットは26文字なので ^Zは1AHです。

あ、なるほど!
ASCIIコード内の制御コードの事をそう呼んでいたのですね。
今まで疑問だった事がやっとわかりました。(^zとかの文字はググれないですしw)
@以降の図形文字に^(Ctrlの略)を付けて64ビット前にあるそれぞれの制御文字の事を表していたんですね。

コマンドプロンプトで間違えてCtrlを押しながらキーを叩くとそれらの^付き文字が表示されます。
なんなんだろーとは思っていましたが、あれは通常は入力できない制御文字を入力したり表示したりする手段だったんですね。
勉強になりました、どうもー。


元々VB6が出た頃からプログラミングを始めた当時の私はEOF(ファイルの終端)が来るとか来ないとか言われても腑に落ちませんでした。
終端は終端であってそんな「もの」は現実に「無かった」わけで、その時はようは「概念上の存在」であると解釈して決着しました。

後に制御文字コードの存在を知り、c言語でヌル終端の存在と意義を知り、物理的なバックアップメディアとしてのテープデバイスの存在を知り、そこまできてやっと「EOF」が昔が「実在した」事とその名残り(?)が今も続いているという現状がなんとなく理解できるようになりましたとさ。

編集 削除
EG  2010-01-30 01:34:55  No: 102194  IP: [192.*.*.*]

ご回答ありがとうございます。

GODさん、サンプルプログラムありがとうございました。
確かに同じエラーが確認できました。ヌル文字だけでなくこういう終端を表す文字のときでもエラーが出るんですね。

ななさん、FileByteReadという関数ありがとうございます。
バイナリで読み込むのって意外に大変なんですね。
活用させていただきたいと思います。

これからはInputB関数を使うときはバイナリモードで開くように心がけたいと思います。また、保存するときはヌル文字やEOFなどが含まれていないかチェックするようにしたいと思います。

おかげさまで解決いたしました。
この度はどうもありがとうございました。

編集 削除