バイナリファイルの読み込み

解決


濱のオジーさん  2007-08-24 19:21:15  No: 27523  IP: 192.*.*.*

VBからdelphiに移植しながら勉強中です。かなり初歩的な質問かと思いますが宜しくご指導ください。VBではあるバイナリファイルの中のあるタグ(2バイト)を探しながらその場所を取得してそこからの相対位置を指定していくつかのデータ(LongとSingle)をGet #F,p,Vlng  などと読み込ませるものを作っていましたがこれで一度に指定した型のデータが得られて便利でした。

Delphiでも同じような方法が何かあるのではないかとread、blockreadなどを試しながらやってみましたが失敗ばかりで質問させていただいた次第です。

なおバイナリファイルは最初の部分は種々雑多なデータ(これは読み込み不要)が混在するもので途中からデータの頭を示すタグ+データの定型化されたものが書かれています。この定型化された部分のデータだけ取得したいと思います。

このような場合どのようにして読み込んだら良いでしょうか。

編集 削除
HOta  2007-08-24 22:23:54  No: 27524  IP: 192.*.*.*

TStreamを使う方法です。

var
  FS :TFileStream;
  Str,Tgt:String;
  i:Integer;
begin
  Tgt := '12';
  FS:=TFileStream.Create('ここは読み込むファイル名',fmOpenRead);
  Try
    SetLength(Str,FS.Size);
    FS.ReadBuffer(Str,FS.Size);
    for i:=1 to Length(Str) do
      if Str[i] = Tgt[1] then
        if Str[i + 1] = Tgt[2] then
          {ここから後ろがデータ}
  Finally
    FS.Free;
  End;
end;

などはどうですか?
動作検証はしていません。

編集 削除
濱のオジーさん  2007-08-25 12:25:16  No: 27525  IP: 192.*.*.*

HOta様、ご回答をありがとうございました。
Str[i]というスタイルで取り出せればあとは何とか工夫して、と思って朝からこれに取り組んでいましたがストリームというものは初めてなので上手くいきません。初歩的と思いますがサンプルコードに関して再度質問させていただきますので宜しくお願いします。
1.下記のコードで試すと「ストリームからの読み込みエラー」で止まります。
2.SetLengthでもStrの配列領域を確保してありStr[i]でデータを取り出していますので配列宣言が必要ではないかと試しましたがわかりませんでした。
3.FS.Sizeを見ようとステップ実行しましたが表示されませんでした。
ストリームというものが抽象的に見えて理解できていませんが宜しくご指導ください。

Procedure Gread(Gfile:string);
var
  FS :TFileStream;
  Str:String; // Array of string;
  Tgt1,Tgt2:String;
  i:Integer;
begin
  Tgt1:= '12'; Tgt2:='10';
  FS:=TFileStream.Create(gfile,fmOpenRead);
  Try
    SetLength(Str,FS.Size);
    FS.ReadBuffer(Str,FS.Size);    /// ここでエラー発生
    //for i:=1 to Length(Str) do
    //  if Str[i] = Tgt1 then
    //    if Str[i + 1] = Tgt2 then
    //      {ここから後ろがデータ}
  Finally
    FS.Free;
  End;
end;


1.

編集 削除
HOta  2007-08-25 16:41:00  No: 27526  IP: 192.*.*.*

失礼しました。

ここはポインターですね。
>    SetLength(Str,FS.Size);
>    FS.ReadBuffer(Str,FS.Size);    /// ここでエラー発生
     FS.ReadBuffer(Pointer(Str)^,FS.Size);
でどうでしょう。

編集 削除
濱のオジーさん  2007-08-25 18:27:25  No: 27527  IP: 192.*.*.*

HOta様、ありがとうございました。
やっとReadBufferが通り、Strの配列があら不思議という感じで確認できました。またバイト配列の方法でも確認しました。ここまでくればあとは当方の少ない知識で力づくでLongInt,Singleなどの値に変換(例:べき乗計算で足していくなど)しようと思いますが、もしもStr配列、またはバイト配列からLongIntなどを取得するスマートな方法がありましたら教えてください。

編集 削除
HOta  2007-08-26 07:32:39  No: 27528  IP: 192.*.*.*

recordで読み込めます。
予め宣言します。
type
  THoge = Packed record    //Packed を付けること
    dataByte:array[0..5] of byte;
    dataLng:LongInt;
  end;
・・・・・・・・・・・・・・・
var
  Hoge :THoge ;
・・・・・・・・・・・・・・・
//開始位置までポジションを移す。HelpのTFileStreamを参照
//タグの終わりに移動
  FS.Seek(タグの終わり,soFromBeginning);
  FS.ReadBuffer(Hoge , sizeof(Hoge ));
//Hoge.dataByte[0]・Hoge.dataLngなどでアクセスできます。

編集 削除
TOBY  URL  2007-08-26 23:06:49  No: 27529  IP: 192.*.*.*

> もしもStr配列、またはバイト配列からLongIntなどを取得するスマートな方法がありましたら教えてください。

えっと、stringやバイト配列に一旦読み込むのではなく、
そのままLongIntで読み込んだらどうでしょうか?

var n: LongInt;
begin
 :
stream.ReadBuffer(n, sizeof(n));
 :

あと、HOta さんのいうように、record使う方法もありますね。
指摘されているように、packedにしないと、構造体の並び方が勝手に最適化されるので注意です。
あと、もし、既存のデータを読む場合、アライメントも考慮しないといけないです。
アライメントについては(alignment)、ヘルプに書いてあったはず……。

編集 削除
エコ  2007-08-27 11:06:02  No: 27530  IP: 192.*.*.*

>えっと、stringやバイト配列に一旦読み込むのではなく、
>そのままLongIntで読み込んだらどうでしょうか?

データ(タグ)位置を見つけるには、stringやバイト配列に一旦読み込まねば...
最初の部分の不要なデータのサイズが固定じゃないからタグ位置を探すんだよね...
ただ、既にタグ位置を見つけるため一度読込んでいるんだから、二度読みはムダ。
type
  PHoge = ^THoge;
  THoge = Packed record
    dataLNG : LongInt;
    dataSGL : Single;
    ・・・・・・・・・
  end;
  ・・・・・・・・・・・・・・・
var
  Hoge: PHoge;
  ・・・・・・・・・・・・・・・
  //タグの次(データ先頭位置)が i+2としたら
  Hoge := @Str[i+2];
  //Hoge.dataLNG・Hoge.dataSGLなどでアクセス。

ただし、これはデータがリトルエンディアンであればの話。
もしもビッグエンディアンなら、もうひと手間必要。

編集 削除
濱のオジーさん  2007-08-27 13:13:28  No: 27531  IP: 192.*.*.*

HOta様、TOBY様、エコ様  貴重なアドバイスをありがとうございました。理解度ほとんどゼロでスタートしましたが昨日から苦戦すること約16時間,お蔭様で読み込みに成功しました。あれやこれや試していたら一時はグチャグチャになってしまいましたが結果的にHOta様のサンプルを基本にエコ様のアイデアをミックスしたようなものとなりました。
下記コードでは2度読みのようですがVBでGET #による一度読みよりもかなり速くなった感じがします。とりあえず(無理やり?)動いていますが間違っている点などありましたらご指摘くださるよう宜しくお願いします。

procedure Gread(gfile:string);

var
  FS:TFileStream;
  b:array of Byte;
  b1,b2:byte;
  i,ii,i5,n,nmem:Integer;

    dataReal:real;
    //dataLng:LongInt;
    //dataSgl:Single;
    dataint:integer;
    

begin

b1:=1; b2:=0; b3:=0; //タグの値
                     
                     //配列のメモリを確保
                     nmem:=100;  
                     gmemset(nmem);

  n:=0;

  FS:=TFileStream.Create(gfile,fmOpenRead);

  Try

    SetLength(b,FS.Size);
    FS.ReadBuffer(Pointer(b)^,FS.Size);

    i5:=length(b)-30;

    for i:=1 to i5 do begin 
      
      if b[i] = b1 then
      if b[i+1] = b2 then
      if b[i+2] = b3 then

      if b[i+24] = b1 then 
      if b[i+25] = b2 then
      if b[i+26] = b3 then

          begin
             n:=n+1;
 
             // ここで必要な配列をリセット
            


             //// DATA1 (INT)
             ii:=i+6;  //DATA1の位置
             FS.Seek(ii,soFromBeginning);
             FS.ReadBuffer(dataint , 4);

             DATA1[n]:=dataint;
             
             //// DATA2 (REAL)
             ii:=i+14;
             FS.Seek(ii,soFromBeginning);
             FS.ReadBuffer(datareal , 8);

             DATA2[n]:=datareal;
          end;
    end;

  Finally
    FS.Free;
  End;

end;

編集 削除
小出しじじぃ  2007-08-27 15:33:38  No: 27532  IP: 192.*.*.*

おやおや何時の間にか、タグが3バイトに変ったり、必要なデータが不連続な並び
になっていたり、SingleがDouble(Real)に変っていたり...
ま、いっか...だけど二度読みはイヤだね。

     //// DATA1 (LongInt)
     DATA1[n]:=PLongInt(@b[i+6])^;
             
     //// DATA2 (Double/REAL)
     DATA2[n]:=PDouble(@b[i+14])^;

編集 削除
濱のオージさん  2007-08-27 18:18:12  No: 27533  IP: 192.*.*.*

小出しじじぃ様、具体的なコードをありがとうございます。ポインター(?)が理解できていないのですがコピペですぐに動くようになり、これで1度読みによるものができました。VBの時(GET#で)は2秒くらい掛かっていたものがDelphiではほとんど一瞬で読み込めるようになり驚いています。

>おやおや何時の間にか、タグが3バイトに変ったり、必要なデータが不連続な>並びになっていたり、SingleがDouble(Real)に変っていたり...

フォーマット不明のファイルの中から規則性を探し出しながらの作業でしたがタグも3バイト目もどうやら毎回同じようである、などと模索しながらやっていましたので失礼しました。とりあえず大成功、皆様に感謝します。ありがとうございました。

編集 削除