tagCANDY CGI VBレスキュー(花ちゃん)の Visual Basic 6.0用 掲示板 [ツリー表示へ]   [Home]
一括表示(VB6.0)
タイトルVB6.0でデータをバイナリ処理
記事No14480
投稿日: 2010/02/22(Mon) 11:14
投稿者あかちん
時々この掲示板の内容を参考にさせてもらってます。
学校現場で現役中にVB6.0でソフトを作ってました。データ処理に以下のような文字列をバイナリ形式で読み書き処理をしてましたが ごく時々データファイルが壊れるとの報告を受けてます。検証してエラーの再現しようとするのですが再現できません。どなたか御教授お願いします。データとアプリケーション(自作)はサーバーに置いてショートカットで実行してます。このような手法は邪道でしょうか
vb歴10年 OS windows 98 xp

Public Datafile As String 'データファイルパス名
Public RW(0 To 367) As String
Sub RWdata() '
       Open Datafile For Binary  As #1
            Get #1, 1, RW()
           'RW()の処理
      Get #1, 1, RW()
       Close #1
End Sub

[ツリー表示へ]
タイトルRe: VB6.0でデータをバイナリ処理
記事No14481
投稿日: 2010/02/22(Mon) 12:51
投稿者るしぇ
> データとアプリケーション(自作)はサーバーに置いて
複数のユーザで使用できそうですが、排他処理はどのように設計されていますか?
> Sub RWdata() '
普通、ReadWriteの意味で使いそうですが、この関数は読むだけで使っているのですか?
このプログラムコードは実際のプログラムを編集しているのでは無いですか?

> 検証してエラーの再現しようとするのですが再現できません。
そうなると、本番環境でログ出力コードを埋め込んで、エラーとなる前後の
出来る限りの情報収集などを考えるでしょうが、何も情報が得られなかったのですか?
そもそも、何を想定して、どういう再現手順でテストして、再現できないと言っているのですか?

[ツリー表示へ]
タイトルRe^2: VB6.0でデータをバイナリ処理
記事No14482
投稿日: 2010/02/22(Mon) 16:18
投稿者あかちん
早速の書き込みありがとうございます。
説明が不十分でした。
アプリケーションは複数のユーザーが閲覧、書き込みが出来ることを目的に作成してあります(簡易グルーウエア)。
データファイルは0.5Mバイト以下の小さいファイルなのでファイルを開いてすぐ閉じるようにしています。

実際のプログラム(浮浪箇所は省略)

標準モジュールで

Public RW(0 To 367) As String
Public Datafile As String 'データファイルパス名

Sub RWdata(Md As String) '

    Select Case Md
    Case "Read"
        Open Datafile For Binary Access Read As #1
            'If RWOkFlg = True Then
                Get #1, 1, RW()
            'End If
        Close #1
  
    Case "Write"
        Open Datafile For Binary Access Write Lock Write As #2
            'If RWOkFlg = True Then
                Put #2, 1, RW()
            'End If
        Close #2
    End Select
End Sub

を作成して
フォームモジュールでRW()を書き換えて(処理)して呼び出します。

当方は自己流(すみません)でリファレンスも勝手な解釈しているので上記のような手法は
どこか大きなミスがあるのではないかと心配しています。一応動作はしているのですが

[ツリー表示へ]
タイトルRe^3: VB6.0でデータをバイナリ処理
記事No14483
投稿日: 2010/02/22(Mon) 18:48
投稿者花ちゃん
投稿される度にコードが変わったり、省略したコードでは、正確な確認もできないし
実際は、どのようなコードを使っておられるのかも定かでないので正確な事は言えませんが
投稿されたコードだけで判断するなら、オープンしたファイルは、変数に読み込んだら
すぐクローズする。(書き込み時はロックモードで開く)

ファイル番号、#1  #2 のような固定の番号は使用しないで、FreeFile 関数 を使って
空いている番号を使用するようにする。

今回のコードを見る限りでは、ファイル番号 の固定が一番問題かと思うので、
FreeFile 関数を使って取得してそれで、一度試してみてはどうでしょうか?

[ツリー表示へ]
タイトルRe^4: VB6.0でデータをバイナリ処理
記事No14484
投稿日: 2010/02/22(Mon) 19:02
投稿者あかちん
貴重なアドバイスありがとうございます
> 投稿される度にコードが変わったり、省略したコードでは、正確な確認もできないし
次回から気をつけたいと思います。

> ファイル番号、#1  #2 のような固定の番号は使用しないで、FreeFile 関数 を使って
> 空いている番号を使用するようにする。

もしかしてファイル番号の解釈に基本的な誤解があるかもしれません。リファレンスを再度読み返してみたいと思います。目から鱗かも
ありがとうございました。

[ツリー表示へ]
タイトルRe^5: VB6.0でデータをバイナリ処理
記事No14485
投稿日: 2010/02/22(Mon) 21:05
投稿者あかちん
ファイル番号について調べてみました。

私は今までfreefile関数はプログラムを書くときのファイル番号重複を防ぐためだと理解していました。メディアがFDのころのエラーによるデータの損傷を防ぐためファイルは開いたらすぐ閉じるようにしていたのでfreefile関数の必要は無かったのです。
ところがmsdnの説明には番号1から255を指定すると他のアプリケーションからアクセス不可になり、256から512ではアクセス可と説明されています。
試しにファイル番号#1で書いた小さなプログラムを複数同時に実行して確かめましたがアクセス(バイナリ 読込)出来るようです。
ますます疑問が増えました。どなたか御教授下さい。乱文で申し訳ありません。

[ツリー表示へ]
タイトルRe^6: VB6.0でデータをバイナリ処理
記事No14486
投稿日: 2010/02/22(Mon) 22:19
投稿者花ちゃん
> 試しにファイル番号#1で書いた小さなプログラムを複数同時に実行して確かめましたが
>アクセス(バイナリ 読込)出来るようです。

どのようなコードを書きましたか?
同時に同じ番号のファイル をオープンしたらエラーになりませんか?

Private Sub Command1_Click()
   Open "c:\test.htm" For Binary As #1
   Open "c:\test.xlsx" For Binary As #1
End Sub

Private Sub Command2_Click()
   Dim Fno As Integer
   Fno = FreeFile()
   Open "c:\test.htm" For Binary As #Fno
   Debug.Print Fno
   Fno = FreeFile()
   Open "c:\test.xlsx" For Binary As #Fno
   Debug.Print Fno
End Sub

>ところがmsdnの説明には番号1から255を指定すると他のアプリケーションから
>アクセス不可になり、256から512ではアクセス可と説明されています。

どこに書いていましたか?


構文
FreeFile(num)
引数numには、0または1を指定します。

解説
Openメソッドで指定する、使用可能なファイル番号を返します。
引数numに0を指定すると、FreeFileは1〜255の範囲のファイル番号を返します。
引数numに1を指定すると、FreeFileは256〜511の範囲のファイル番号を返します。
引数numを省略すると、0が指定されたものとみなします。
Openメソッドで、既に使用されているファイル番号を指定すると、実行時エラーが発生します。FreeFile関数を使うことで、ファイル番号の重複を避けることができます。

[ツリー表示へ]
タイトルRe^6: VB6.0でデータをバイナリ処理
記事No14487
投稿日: 2010/02/22(Mon) 23:24
投稿者魔界の仮面弁士
> 私は今までfreefile関数はプログラムを書くときのファイル番号重複を防ぐためだと
FreeFile は空き番号を返しますが、Open 操作を伴うわけではないため、
それ単体では番号を管理することはできません。最終的には固定番号でも FreeFile でも、
管理が正しくできていれば動作上は問題無いかと。

ただし、作成したコードを流用する際の使いまわしなどを考慮すれば、
固定番号よりも FreeFile で管理した方が、使い勝手は良いと思います。

> ところがmsdnの説明には番号1から255を指定すると他のアプリケーションから
> アクセス不可になり、256から512ではアクセス可と説明されています。
あれ、「256 〜 511 のファイル番号」じゃありませんでしたっけ。
その説明は、MSDN のどこにありましたか?

511 までの方なら、Visual Basic リファレンスの用語説明にありますね。

-------------
【ファイル番号】
 Open ステートメントを使ってファイルを開くときに、そのファイルに割り当てられる番号。
 1 〜 255 のファイル番号を割り当てると、そのファイルは他のアプリケーションからは
 アクセスできなくなります。256 〜 511 のファイル番号を割り当てると、そのファイルは
 他のアプリケーションからでも、アクセスできるようになります。
-------------

現在では上記の意味は無かったように記憶していますが、今でも
FreeFile(0) と FreeFile(1) の番号体系の違いとして残っていますね。


MSDN の解説には、VB6 には該当していない内容も時折混じっているので、
それと同じような物では無いですかね。

たとえば、Macintosh 専用の「MacID 関数」の解説とか、
16 bit 環境専用の「FileAttr 関数」の第二引数の話とか、
単体の VB では使えないはずの「Option Private ステートメント」とか、
Access VBA 専用の「Option Compare Database」とか。。。


> 複数同時に実行して確かめましたがアクセス(バイナリ 読込)出来るようです。
外部からアクセス可能かどうかは、Open ステートメントで指定してください。


> どなたか御教授下さい。
http://www.tt.rim.or.jp/~rudyard/torii009.html

[ツリー表示へ]
タイトルRe^3: VB6.0でデータをバイナリ処理
記事No14488
投稿日: 2010/02/23(Tue) 07:35
投稿者あかちん
花ちゃんさん、魔界の仮面弁士さんありがとうございました。
おかげさまでFreeFile関数については解決です。何でもそうなんですがステートメントや関数などはそれを開発した意図をしっかりと理解して使用しないといけないですね。

> あれ、「256 〜 511 のファイル番号」じゃありませんでしたっけ。
その通りです。失礼しました。

> MSDN の解説には、VB6 には該当していない内容も時折混じっているので、
> それと同じような物では無いですかね。
そうなんですか、プログラマーに時間の無駄をさせますね。

ところで本題は以下のコード(バイナリファイルに文字列配列を読み書き)は正常でしょうかということでした。ごくまれにファイルの配列の要素が結合したりとかファイルが大きくなったりします。再現できません。

この手法にたどり着いた理由は
1) Split、Join関数を使ってデータを処理するためランダムファイルに適さない。
2) 心ないユーザーがファイルをノートパッドなどで書き換えた場合にエラーを発生させる。
3) VerUpが容易
などです。

Public RW(0 To 367) As String
Public Datafile As String 'データファイルパス名

Sub RWdata(Md As String) '

    Select Case Md
    Case "Read"
        Open Datafile For Binary Access Read As #1
            'If RWOkFlg = True Then
                Get #1, 1, RW()
            'End If
        Close #1
  
    Case "Write"
        Open Datafile For Binary Access Write Lock Write As #2
            'If RWOkFlg = True Then
                Put #2, 1, RW()
            'End If
        Close #2
    End Select
End Sub

ご教示願います(確かに「御教授」はおかしいですね。お恥ずかしい次第です)

[ツリー表示へ]
タイトルRe^4: VB6.0でデータをバイナリ処理
記事No14489
投稿日: 2010/02/23(Tue) 10:34
投稿者魔界の仮面弁士
> ごくまれにファイルの配列の要素が結合したりとかファイルが大きくなったりします。
Put # ステートメントは、データ量が多くなれば、元のファイルも大きくなります。
その逆に、ファイルサイズが小さくなる事は無い点にも注意してください。

それは考慮済みという事であれば、Remote Desktop による複数人同時ログオンや
アプリの同時起動などにより、同一ファイルへの同時アクセスが発生していないかを
確認してみてください。


後は、ファイルの内容はモジュールレベル変数(この場合はグローバル変数)で
保持しているようですが、その変数を別の場所で編集している箇所が無いかも
チェックしてみてください。変数 RW を直接編集していなかったとしても、それを
どこかの Sub/Function 引数に渡していて、その引数に ByVal をつけ忘れたまま
引数を編集してしまい、その結果が RW にも意図せず反映されてしまっているとか。


> Open Datafile For Binary Access Read As #1
> Open Datafile For Binary Access Write Lock Write As #2
書きこみ時には「Lock Write」していますが、その反面、
読み込み時には未指定ですが、それは意図的な物ですか?


> データ処理に以下のような文字列をバイナリ形式で読み書き処理をしてましたが
> Get #1, 1, RW()
> Put #2, 1, RW()
この方法は、テキストファイルへの読み書きには使えませんが、その点は大丈夫ですか?
また、各要素の文字列長が短くなった場合、末尾にごみデータが残る可能性がありますが、
その点もクリアされているのでしょうか?


たとえば、
 Erase RW
 RW(0) = "あいうえお!"
 RW(1) = "かきくけこ"
 RW(367) = "Sample Data"
 RWdata "Write"
とした場合、結果ファイルにはデータ長を示す 2 バイトの記述子が書きこまれるため、
    1バイト目: 0B,00                            : 「11バイト」を表す
    3バイト目: 82,A0,82,A2,82,A4,82,A6,82,A8,21 :  「あいうえお!」を表す
   14バイト目: 0A,00                            : 「10バイト」を表す
   16バイト目: 82,A9,82,AB,82,AD,82,AF,82,B1    :  「かきくけこ」を表す
   26バイト目: 00,00                            : 「0バイト」を表す
   28バイト目: 00,00                            : 「0バイト」を表す
   30バイト目: 00,00                            : 「0バイト」を表す(以降繰り返し)
             :
  756バイト目: 0B,00                            : 「11バイト」を表す
  758バイト目: 53,61,6D,70,6C,65,20,44,61,74,61 :  「Sample Data」を表す
というバイナリファイルが生成されます。
(00 等はテキストデータとして扱えないため、バイナリファイルとなります)


そしてさらにこの後、
 Erase RW
 RW(0) = "TEST"
 RW(367) = "DATA"
 RWdata "Write"
というデータが書き込まれると
    1バイト目: 04,00                            : 「4バイト」を表す
    3バイト目: 54,45,53,54                      :  「TEST」を表す
    7バイト目: 00,00                            : 「0バイト」を表す
    9バイト目: 00,00                            : 「0バイト」を表す
   11バイト目: 00,00                            : 「0バイト」を表す(以降繰り返し)
             :
  739バイト目: 04,00                            : 「4バイト」を表す
  741バイト目: 44,41,54,41                      :  「DATA」を表す
  745バイト目: ※これ以降には、前回書き込み時のファイル内容がそのまま残っている※
という結果になります。

[ツリー表示へ]
タイトルRe^5: VB6.0でデータをバイナリ処理
記事No14494
投稿日: 2010/02/23(Tue) 15:18
投稿者あかちん
魔界の仮面弁士さん とても丁寧な解説をありがとうございます

以下の3点でとても参考になりました。
1 同時アクセスの可能性
> それは考慮済みという事であれば、Remote Desktop による複数人同時ログオンや
> アプリの同時起動などにより、同一ファイルへの同時アクセスが発生していないかを
> 確認してみてください。

 対策 ファイルは開いてすぐに閉じます。やはり不十分ですよね

2  Lock Writeについて
> > Open Datafile For Binary Access Read As #1
> > Open Datafile For Binary Access Write Lock Write As #2
> 書きこみ時には「Lock Write」していますが、その反面、
> 読み込み時には未指定ですが、それは意図的な物ですか?

 読込にはlockが必要無いと思っていたのですがどうも大きなミスらしい

3 末尾のゴミ
> > Get #1, 1, RW()
> > Put #2, 1, RW()
> この方法は、テキストファイルへの読み書きには使えませんが、その点は大丈夫ですか?
> また、各要素の文字列長が短くなった場合、末尾にごみデータが残る可能性があります

 この件については全く予想してませんでした。この場合書き込んだデータを読み込むとき書き込んだデータと異なるデータが返される事になるのでしょうか。
ちなみに書き込むデータは
join関数で普段使用しない"|"や"^"でテキストデータを結合(?)して
rw(0)="|ア行^あ^い^う^え^お|カ行^か^き^き^け^こ||||||"つぃます。
 このような文字列を書き込んで 読み込むときにsplit関数で分解(?)するようにしています。自己流も甚だしいと思いますが 学校現場にあったレベルだと思っています。
 
以上を参考にコード修正を検討していきたいと思っています。解決したらまたこの場をお借りして報告したいと思っています。すでにリタイヤしているがこのような作業は楽しみです。

[ツリー表示へ]
タイトルRe^6: VB6.0でデータをバイナリ処理
記事No14495
投稿日: 2010/02/23(Tue) 16:04
投稿者魔界の仮面弁士
> 対策 ファイルは開いてすぐに閉じます。やはり不十分ですよね
読み書きするファイルが、サーバー上の共有ファイルであるような場合には、
排他制御は特に重要だと思います。


> 読込にはlockが必要無いと思っていたのですがどうも大きなミスらしい
開く際に Lock を指定して排他オープンすれば、同時アクセスを完全にブロックできます。
もしもロックされているファイルを開こうとした場合、Open する際に実行時エラーが
発生しますので、それをプログラムからトラップすることができます。

一方、Shared にしていた場合には同時アクセスが可能となるため、
データを読み込んでいる最中に、データが編集されてしまう事もありえます。


> 末尾のゴミ
これを避ける場合には、
 (案1)常にファイルサイズが一定となるような固定長データで保持する。
 (案2)出力する際には、ファイルを作り直すようにする。
 (案3)ADODB.Stream で出力するように変更し、SetEOS メソッドで切り詰める。
などの対策があります。


> ちなみに書き込むデータは
> join関数で普段使用しない"|"や"^"でテキストデータを結合(?)して
> rw(0)="|ア行^あ^い^う^え^お|カ行^か^き^き^け^こ||||||"つぃます。
個々の要素内には、どのような文字を含めても OK です。
データ中に改行や NULL 文字を含めても構いません。
 RW(0) = "あ" & vbNullChar & "い" & vbCrLf & "う" & vbTab & "え"

ただし、vbNullString と "" は区別されないため、
 RW(0) = ""
として書き込んでも、結果は vbNullString になります。

[ツリー表示へ]
タイトルRe^7: VB6.0でデータをバイナリ処理
記事No14496
投稿日: 2010/02/24(Wed) 06:16
投稿者あかちん
魔界の仮面弁士さん ありがとうございます。

 データをアクセスのデータベースにするともっと安全に運用できると思いますがトラブルが起こったときに現場で対応できるようにすることを前提にしているためこのようなオリジナルのデータファイルに読み書きする形になってます。(vb6で.mdbにアクセスするプログラムはとても快適だった。)

 実際に壊れたファイルを皆さんからいただいたアドバイスを元にもう一度楽しみながら分析してみたいと思います。壊れた原因がはっきりしたときなどに再度投稿したいと思います。ありがとうございました。

[ツリー表示へ]
タイトルRe^4: VB6.0でデータをバイナリ処理
記事No14490
投稿日: 2010/02/23(Tue) 12:02
投稿者るしぇ
> 2) 心ないユーザーがファイルをノートパッドなどで書き換えた場合にエラーを発生させる。
出力ファイルはユーザの所有物だと思っているし、結局、テキストデータの保存なのだから
ノートパッドで書き換える事も要件に入ってもおかしくないレベル。
# INI ファイルを直接編集してアプリの初期設定を変えるなんて普通のことだし、
# わざわざアプリを立ち上げないと設定を変えられないのは、アプリが未熟とされ
# てもおかしくない。

バイナリで書き込んでも編集は可能だし、ファイル丸ごと上書きしてもいいし、
そのデータがアプリで受け入れられる形かどうかチェックできないのは、アプリの
機能が低いだけなのだろうと思いました。

[ツリー表示へ]
タイトルRe^5: VB6.0でデータをバイナリ処理
記事No14491
投稿日: 2010/02/23(Tue) 12:37
投稿者あかちん
みなさん 貴重なコメントありがとうございます。
ただいま仕事中なので後ほど皆さんのコメントを熟読したいと思います。
とりあえずお礼を申し上げます

[ツリー表示へ]