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

構造体データのバイナリI/O

環境/言語:[Windows 7 / VB.NET ( VB 2010 Express )]
分類:[.NET]

構造体(ExampleSt):変数 ExDt
Public Structure ExampleSt
    Dim name As String
    Dim byte_val1 As Byte
    Dim byte_val2 As Byte
    Dim short_val As Short
    Dim int_val As Integer
    Public Sub Initialize()
        name = Microsoft.VisualBasic.Space(32)
        byte_val1 = 0
        byte_val2 = 0
        short_val = 0
        int_val = 0
    End Sub
End Structure

プログラムで保持している上記データをバイナリファイルとしてライト/リードしたいのですが、
Stringデータ(半角英数字と全角文字の組み合わせ)のリードがうまくいきません。

【ライト時】
    Dim OutByte() As Byte
    Dim ByteStr() As Byte
    Dim dimpt As Integer

    Redim OutByte(40) '=32+1+1+2+4

    For loop1 As Integer = 0 To 39
        OutByte(loop1) = 0
    Next

    dimpt = 0

    'Stringデータの書き込み
    Redim ByteStr(32)
    ByteStr = System.Text.Encoding.GetEncoding(932).GetBytes(ExDt.name)
    For loop2 As Integer = 0 To System.Text.Encoding.GetEncoding(932).GetByteCount(ExDt.name) - 1
        OutByte(dimpt + loop2) = ByteStr(loop2)
    Next
    dimpt = dimpt + 32

    'Byteデータの書き込み
    OutByte(dimpt) = ExDt.byte_val1
    dimpt = dimpt + 1
    OutByte(dimpt) = ExDt.byte_val2
    dimpt = dimpt + 1

    'Shortデータの書き込み
    OutByte(dimpt + 1) = ExDt.short_val \ 256
    OutByte(dimpt) = ExDt.short_val Mod 256
    dimpt = dimpt + 2

    'Integerデータの書き込み
    OutByte(dimpt + 3) = ExDt.int_val \ 16777216
    OutByte(dimpt + 2) = (ExDt.int_val - OutByte(dimpt + 3) * 16777216) \ 65536
    OutByte(dimpt + 1) = (ExDt.int_val - OutByte(dimpt + 3) * 16777216 - OutByte(dimpt + 2) * 65536) \ 256
    OutByte(dimpt) = ExDt.int_val Mod 256
    dimpt = dimpt + 4

    File.WriteAllBytes("SaveData.bin", OutByte)

上記"SaveData.bin"をバイナリエディタで参照するとちゃんと保存できています。
nameは32文字に満たない部分は0になっています。

【リード時】
    Dim InpByte() As Byte
    Dim ByteStr() As Byte
    Dim dimpt As Integer
    Dim ExDt As New ExampleSt

    Redim InpByte(40) '=32+1+1+2+4
    InpByte = My.Computer.FileSystem.ReadAllBytes("SaveData.bin")

    ExDt.Initialize()

    'Stringデータの読み込み
    ReDim ByteStr(32)
    For loop1 As Integer = 0 To 31
        ByteStr(loop1) = InpByte(dimpt + loop1)
    Next
    ExDt.name = System.Text.Encoding.GetEncoding(932).GetString(ByteStr)
    dimpt = dimpt + 32

    'Byteデータの読み込み
    ExDt.byte_val1 = InpByte(dimpt)
    dimpt = dimpt + 1
    ExDt.byte_val2 = InpByte(dimpt)
    dimpt = dimpt + 1

    'Shortデータの読み込み
    ExDt.short_val = InpByte(dimpt + 1) * 256 + InpByte(dimpt)
    dimpt = dimpt + 2

    'Integerデータの読み込み
    ExDt.int_val = InpByte(dimpt + 3) * 16777216 + _
                          InpByte(dimpt + 2) * 65536 + _
                          InpByte(dimpt + 1) * 256 + _
                          InpByte(dimpt)
    dimpt = dimpt + 4

ExDt.nameをコントロール(TextBoxなど)に書き出すと文字列の後ろにスペースが入ってしまいます。
文字列長(.Length)も文字によってバラバラですが32に近い値になっています。
中には32を越えるものも…。
例:"よしと"→30、"けん"→31、"まさよし"→29、"Baycom"→33、"ドスパラサポートセンター"→21

どこが悪いのでしょうか?ご存知の方、お教えください。よろしくお願いします。

ShortデータやIntegerデータは上記でうまくいっているようですが、
他にいい方法があればそちらも併せてよろしくお願いします。
2012/04/12(Thu) 09:57:04 編集(投稿者)

■No30285に返信(はるさんの記事)
> バイナリファイルとしてライト/リードしたいのですが
BinaryWriter/BinaryReaderクラスを使うのは如何でしょうか。


以下、細かいコードの意味(ロジック)までは追っていないのですが、
VB のコードとして不自然だな、と思った点を列挙してきます。


> Redim OutByte(40) '=32+1+1+2+4
これは、0〜40 までの「41」個の要素を用意することになります。
32+1+1+2+4 なら「40」なのではありませんか?


> For loop1 As Integer = 0 To 39
>  OutByte(loop1) = 0
> Next
ReDim した時点で、自動的に初期値 0 がセットされていますよ。

あえて明示的に初期化したいなら、OutByte(40) も初期化すべきなので、
「To 39」のようなマジックナンバーではなく、「To OutByte.GetUpperBound(0)」とか
「To UBound(OutByte)」とか「To OutByte.Length - 1」などの記述を使うべきかと。


> Redim ByteStr(32)
> ByteStr = System.Text.Encoding.GetEncoding(932).GetBytes(ExDt.name)
この場合、直前の ReDim は無意味です。
その直後に、.GetBytes で生成された別の配列をセットしなおしていますから。


> For loop2 As Integer = 0 To System.Text.Encoding.GetEncoding(932).GetByteCount(ExDt.name) - 1
これだと、Encoding クラスによる変換処理を再度行う事になります。
変換結果は既に ByteStr に入っているのですから、GetByteCount を使うよりは、
上記 OutByte 同様、ByteStr のサイズを調べた方が良いかと思いますよ。


> OutByte(dimpt + 2) = (ExDt.int_val - OutByte(dimpt + 3) * 16777216) \ 65536
前後の処理を良く読んでいないのですが、上記のような式だと、
Option Strict On モードでは型変換不足でエラーになりそうです。

また、Option Strict Off だとしても、
 ・「OutByte(dimpt + 3)」が 128 以上だと、「OutByte(dimpt + 3) * 16777216」が
  Integer の範囲を超えるため、演算に失敗する。

 ・代入式の右辺が0未満または 255 を超えると、Byte 型への代入に失敗する。
という結果になりえます。そういう値になる可能性があるかどうかは分かりませんが。


> ExDt.nameをコントロール(TextBoxなど)に書き出すと
復元前の ByteStr を、
 TextBox1.Text = BitConverter.ToString(ByteStr)
などで出力して、意図したバイナリが得られているかをチェックしておきましょう。


> 文字列の後ろにスペースが入ってしまいます。
固定長 Byte 配列を String にデコードする際、末尾の 0 はどのように処理しましたか?
そのままだと vbNullChar に変換されますので、たとえば、
 Dim bin(0 To 63) As Byte
 Dim s As String = System.Text.Encoding.GetEncoding(932).GetString(bin)
 MsgBox(s.Length)
の場合、画面には「64」と表示されますよね。

テキストボックス等は、テキストを NULL 終端文字列として扱うため、
多くのコントロールでは、vbNullChar 以降の文字は表示されないのですが、
コントロール任せにはせず、自前で処理しておいた方が望ましいかと思います。
たくさんの指摘ありがとうございます。

ひとつひとつ確認しながら修正&改良していく際の参考にさせていただきます。

また不明点があったら質問させていただきます。
BitConverter.ToString を使ってみたところ、ByteStr内の文字列の後ろに0が表示されていました。

いくつか指摘を受けて現在確認作業中ですが、以下の修正でとりあえずは望む結果が得られました。

    'Stringデータの読み込み
    ReDim ByteStr(32)
    For loop1 As Integer = 0 To 31
        ByteStr(loop1) = InpByte(dimpt + loop1)
    Next
    ExDt.name = System.Text.Encoding.GetEncoding(932).GetString(ByteStr)
    dimpt = dimpt + 32

      ↓

    'Stringデータの読み込み
    ReDim ByteStr(32)
    For loop1 As Integer = 0 To 31
        If InpByte(dimpt + loop1) = 0 Then
            Exit For
        End If
        ByteStr(loop1) = InpByte(dimpt + loop1)
    Next
    ReDim Preserve ByteStr(loop - 1)
    ExDt.name = System.Text.Encoding.GetEncoding(932).GetString(ByteStr)
    dimpt = dimpt + 32

まだまだ勉強不足なので、いろいろ試行錯誤しつつ頑張ってみます。

ありがとうございました。
解決済み!

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