バイト配列から変数切り出し

解決


こうすけ  2008-08-11 17:59:14  No: 100982  IP: 192.*.*.*

VB6を使用しています。バイト配列を試しています。
今まではバイナリファイルからGet#で一文字づつ読んでデータの頭のタグ、ターミネーターのタグをを探し、そこからの相対位置から文字列、Long,Dblなどの変数を読み取っていました。(タグ間のバイト数は一定)正確に動くのですが遅いという欠点がありました。

Dim VInt as integer
Dim VLong as Long
Dim VDbl as Double
Dim sName as String * 16 (1バイト文字)
Dim i,i2



Get #1,i,sName
Get #1,i + 12, VDbl
Get #1,i + 16, VLng
Get #1,i + 20, VInt

のようにしていました。

これをバイト配列で一挙に読むように
Dim buf() As Byte
Dim LByte As Long

Open sFILE For Binary As #1
  LByte = LOF(1)
  If LOF(1) Then
    ReDim buf(1 To LOF(1))
    Get #1, , buf 
  End If
Close #1

のようにしてみました。この中からsNameを切り出すのに
sName = ""
For i2 = 1 to 16
    sName = sName & Chr(buf(i + i2))
Next i2

のようにしてみましたが何かスマートな方法がありそうな気がして質問させていただきました。  よろしくお願いします。

編集 削除
8月  2008-08-11 18:07:03  No: 100983  IP: 192.*.*.*

ユーザー定義型 を使ってみたら如何でしょうか?

http://madia.world.coocan.jp/cgi-bin/VBBBS/wwwlng.cgi?print+200804/08040016.txt

編集 削除
こうすけ  2008-08-11 19:09:19  No: 100984  IP: 192.*.*.*

8月さまありがとうございます。
ユーザー定義型というものを使ったことがないのでこれから勉強します。コードを眺めながらの疑問なのですが読もうとするバイナリファイルには解読できていない各種のデータが含まれており、その中から特定の頭のタグとターミネーターを探し出してこの間にある素性のわかったデータだけ取り出そうとしていますがこのような場合でもご紹介いただいたサンプルのように処理できるのでしょうか?  テストしていないでの再質問ですがよろしくお願いします。

編集 削除
8月  2008-08-11 19:31:10  No: 100985  IP: 192.*.*.*

1レコードの中身がどうなっているか、すなわち
データ構造が解析されていないと無理かも知れませんね。

しかし元のバイナリファイルがデタラメにデータを書き出している
とも思えないので、解析をしてみたら如何ですか?

Get #1,i,sName
Get #1,i + 12, VDbl
Get #1,i + 16, VLng
Get #1,i + 20, VInt

...として読み込んでいたと言う事は1レコードのサイズと
データの中身が規則的に出力されていると言うことの様に思えるのですが。

編集 削除
8月  2008-08-11 19:35:36  No: 100986  IP: 192.*.*.*

Get #1,i,sName
Get #1,i + 12, VDbl
Get #1,i + 16, VLng
Get #1,i + 20, VInt
..として読み込んだデータの1レコード分を16進数でダンプで
提示してみて下さい。
その上で

>その中から特定の頭のタグとターミネーターを探し出してこの間に
>ある素性のわかったデータだけ取り出そうとしていますが

サンプルとしてのデータの中の
「特定の頭のタグ」と「ターミネーター」を書いて見て下さい。

編集 削除
こうすけ  2008-08-11 19:49:03  No: 100987  IP: 192.*.*.*

8月さま、クイックレスポンスに感謝します。
元のバイナリデータはもちろんデタラメではないと思いますがサイズなど数多くのケースがあり周期性などが分からず、確実に分かったのはタグ(頭と終わり)とその間のデータの構造です。  これを探すのに

Dim i as Integer などなどの変数

i=0
do while i < Lof(i)  −  30
                I = I + 1
                Get #1, I, B1
                If B1 = 84 Then  '頭を探す
                     Get #1, i + 25, B1
                     If B1 = 1 Then  'ターミネーターを探す(確認のため)
                      
のようにして必要とするデータ区間を確定してからデータを取り出していました。質問時のコード  i  は頭の位置です。

編集 削除
こうすけ  2008-08-12 11:06:33  No: 100988  IP: 192.*.*.*

8月さま。お世話になります。メールが時間的に行き違ったようで失礼しました。
現在はデータのあり場所はかなり正確に把握できています。そしてデータ部分の読み取りであれば各データのオフセットは分かっております。従って従来通りのGet #1,i,VLng  でやればLong(例)を取れる状態にあります。現在わからないのはバイト配列からVLngなどを切り出す方法ということを質問させてもらったものです。例えば  

sName = ""
For i2 = 1 to 16
    sName = sName & Chr(buf(i + i2))
Next i2  

D = "&H"
for i2 = 4 to 1 step -1
D = D & Hex(Buf(i+i2)
next i2
VLng = Val(D)

みたいにやれば取得できるのですが何ともダサいやり方ではないかと思いました。

実際のデータは
(String)    54 41 42 43 44 45 46 00 (54が頭で00が尻尾  ABCDEF)

(Long)  下記3データブロック       

42 51 18 40 bc 4a 61 00 01 26 59 89 48 00 00 c0 
6f 51 18 c0 6a 4c 61 00 01 52 5a 89 48 00 00 c0 
90 51 18 00 44 4e 61 00 01 7e 5b 89 48 00 00 00
-----------             ** ----------- ** ** 
            -----------

** = マーカー 
---------- = Long  

というような構造です。よろしくお願いします。

編集 削除
8月  2008-08-12 14:17:53  No: 100989  IP: 192.*.*.*

ユーザー定義型を使うには1レコードの長さが固定である必要があります。
バイナリーファイルのサイズが1レコードの長さで割り切れますか?
ヘッダー部があるならそのサイズを引いた残りのサイズが1レコードの長さで割り切れますか?

ちなみに現在処理仕様としているバイナリーファイルのサイズは?
何レコードあると思われていますか?

> 実際のデータは
>(String)    54 41 42 43 44 45 46 00 (54が頭で00が尻尾  ABCDEF)

上記は下記の「3データブロック」の中には含まれていませんが
どう様に関連しているのでしょうか?
下記の先頭についている??

>(Long)  下記3データブロック       

> 42 51 18 40 bc 4a 61 00 01 26 59 89 48 00 00 c0 
> 6f 51 18 c0 6a 4c 61 00 01 52 5a 89 48 00 00 c0 
> 90 51 18 00 44 4e 61 00 01 7e 5b 89 48 00 00 00

上記で3レコード分と言う事でしょうか?

VB6だと
Get #1,i,sName          これは何バイト?
Get #1,i + 12, VDbl    8バイト
Get #1,i + 16, VLng    4バイト  
Get #1,i + 20, VInt    2バイト...かと思うのですが。

私が書いて欲しかったのは例えば下記の様に
1レコード分のデータ構造です。

90 51 18 00 44 4e 61 00 01 7e 5b 89 48 00 00 00
----------------- ========== *********** +++++
sName             VDbl       VLng        VInt


どなたか他の詳しい方にフォローしてもらった方が良いかも知れませんね。

編集 削除
こうすけ  2008-08-12 15:55:17  No: 100990  IP: 192.*.*.*

先に書きましたのは下記の意味です。

41 42 43 44 45 46 =>String
90 51 18 00 =>Long
44 4e 61 00 =>Long
7e 5b 89 48 =>Long

Dim sName as String * 16 (1バイト文字x16)

オフセットが分かっていますので  Get #1,i + 16, VLng などとやれば取得できています。  これをバイト配列で一括読み込みにしたいと思っていたものです。

編集 削除
魔界の仮面弁士  2008-08-13 20:51:03  No: 100991  IP: 192.*.*.*

>>>>>>>>> 正確に動くのですが遅いという欠点がありました。
>>>>>>>>> Dim sName as String * 16 (1バイト文字)
>>>>>>>>> sName = ""
>>>>>>>>> For i2 = 1 to 16
>>>>>>>>>     sName = sName & Chr(buf(i + i2))
>>>>>>>>> Next i2

? そのような書き方はできないはずですが…。
固定長文字列型である以上、
  Dim sName As String * 16
  sName = ""
  sName = sName & "A"
  sName = sName & "B"
のようなコードを書いても、その結果は Space(16) になってしまうでしょうし。


>>> (String)    54 41 42 43 44 45 46 00 (54が頭で00が尻尾  ABCDEF)
これをバイナリ全体から探し出すために、InStrB は使えないでしょうか。

  '54 41 42 43 44 45 46 00
  Dim search() As Byte
  search = StrConv("TABCDEF" & vbNullChar, vbFromUnicode)

  Dim pos As Integer
  pos = InStrB(1, buf, search, vbBinaryCompare)


>>> メールが時間的に行き違ったようで失礼しました。
掲示板以外にも、メールでのやりとりがあったのでしょうか?


>> ユーザー定義型を使うには1レコードの長さが固定である必要があります。
それは、ランダムアクセスの場合では無いでしょうか。
バイナリアクセスの場合は、そのような制限は無いと思います。

編集 削除
8月  2008-08-14 06:00:34  No: 100992  IP: 192.*.*.*

魔界の仮面弁士さん、

私には今までのやりとりでは
1レコードが固定長なのか、可変長なのか判断がつきかねます。

>>> メールが時間的に行き違ったようで失礼しました。
> 掲示板以外にも、メールでのやりとりがあったのでしょうか?

いえ、この掲示板以外のメールでのやり取り有りません。  
ここでの書き込みを「メール」と誤記している様です。

>> ユーザー定義型を使うには1レコードの長さが固定である必要があります。
> それは、ランダムアクセスの場合では無いでしょうか。
> バイナリアクセスの場合は、そのような制限は無いと思います。

私のスキル不足のようです。失礼しました。

編集 削除
こうすけ  2008-08-17 12:13:03  No: 100993  IP: 192.*.*.*

8月さま、魔界の仮面弁士さま  レスをありがとうございます。

>>> メールが時間的に行き違ったようで失礼しました。
> 掲示板以外にも、メールでのやりとりがあったのでしょうか?

一生懸命考えて書いている間に8月さまからご回答をいただいてたということです。メールではなく書き込みです。失礼しました。

改めて最初の質問にもどさせていただきますと現在はバイト配列を下記のようにしてStringやLongにしようとしていたのですが1バイトづつ取り出して処理するという方法以外に何か上手い方法があったら教えていただきたいと質問したものです。

Dim sName as String (本来求めるStringは可変長なのですが最初の質問時に固定長としたのはGet #1,i,sNameという風に書いてsName = Left(sName, LStr)として取り出そうと考えたものです)
Dim D as String
Dim VLng as Long
Dim LStr as Integer

sName = ""
LStr = 8
For i2 = 1 to LStr
    sName = sName & Chr(buf(i + i2))
Next i2  

D = "&H"
for i2 = 4 to 1 step -1
D = D & Hex(Buf(i+i2)
next i2
VLng = Val(D)

私の知らないだけで世の中には1行で書けるような関数でもあるのではないかと思ったのですがなかなか難しそうだと実感しました。
バイト配列から文字列を探すInstrB関数は便利そうなので今後使っていきたいと思います。  ありがとうございました。

編集 削除
 2008-08-17 12:49:43  No: 100994  IP: 192.*.*.*

Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Private Type L_Long
    lng As Long
End Type

Private Sub Command1_Click()
    Dim src(10) As Long
    Dim dest As L_Long
    
    src(0) = &H64
    src(1) = 0
    src(2) = 0
    src(3) = 0
    
    Call CopyMemory(dest, src(0), 4)
    Debug.Print dest.lng
End Sub


こういう話でいいのかな?
#普段使ってるわけではないので間違ってたらごめん。

編集 削除
 2008-08-18 10:05:10  No: 100995  IP: 192.*.*.*

訂正
>Dim src(10) As Long
>Dim dest As L_Long
Dim src(10) As Byte  
Dim dest As Long     '構造体にしなくても別に良かったですね
    

Debug.Print dest

編集 削除
こうすけ  2008-08-18 11:18:08  No: 100996  IP: 192.*.*.*

と  様。  色々とありがとうございました。最初はsrc(1)、src(2)などをいじっても数値が変わりませんでしたが下記のように src(10) as Byte にしてみたらおかげさまでLONGが得られました。これでLONGについてはOKですので同じ要領でDoubleなどを試してみます。オフセットがわかっていますのでこれで一発で答が出るようになるものと思います。  ありがとうございました。

'Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Private Sub Command1_Click()
    Dim src(10) As Byte 'Long
    Dim dest As Long
    
    src(0) = &HA9
    src(1) = &H2
    src(2) = 0
    src(3) = 0            '正解は681
    
    Call CopyMemory(dest, src(0), 4)
    
    Debug.Print dest
End Sub

編集 削除
こうすけ  2008-08-18 12:25:33  No: 100997  IP: 192.*.*.*

追伸:  バイト配列にして実装したところ以前のバイト毎の拾い読みに較べて比較にならないほど高速になりました。  皆様ありがとうございました。

編集 削除