バイナリデータの切り抜き保存

解決


玲奈  2013-04-13 03:22:02  No: 44302

こんばんは。

Windows7でXE3を使っています。

バイナリデータ(約200KB)の一部分を切り抜いて、
切り抜いたものを別のバイナリデータとして保存したいと考えています。

とりあえず、抜き出した部分を画面に表示させれたものの、
オフセットの値を大きく(例えば20000)すると読み取り違反となってしまいます。

切り抜き方に大きく問題があるとは思うのですが、
どうすれば良いのか、正解が見つかりません。

教えて頂けないでしょうか。
よろしくお願いいたします。

var
  FileName : string;
  ReadLength: Longint;
  I: Integer;
  Stream: TFileStream;
  DataOffset,CutLength : integer;  //切り抜き開始場所,切り抜く長さ
  Buffer: array [0..255] of Byte;
begin
  FileName :='test.dat';
  DataOffset := 200;
  CutLength := 16;

  Stream := TFileStream.Create(FileName, fmOpenRead);
  try
    while True do begin
      ReadLength := Stream.Read(Buffer, SizeOf(Buffer));
      if ReadLength = 0 then
        Break;
      for I := (DataOffset) to (DataOffset+CutLength-1) do begin
        (* 16進数で画面に出力 *)
        Edit1.Text :=Edit1.Text + (Format('%.2x ', [Buffer[I]]));
      end;
      Break;
    end;
  finally
    FreeAndNil(Stream);
  end;
  //このあとでEdit1.textを元にバイナリデータを作る
end;


Nov  2013-04-13 04:11:06  No: 44303

whileループは必要ありません。

(1) DataOffsetの位置にStream.Seek。
(2) CutLength(<SizeOf(Buffer))の長さだけSteam.Read。

表示方法は色々ありますが、配列の添字が定義サイズを超えてはいけません。


玲奈  2013-04-13 04:54:35  No: 44304

Novさん、ありがとうございました。

whileの除去はできたのですが、Stream.Seekと CutLength について、
もう少し詳細に教えて頂けないでしょうか。
検索してみたものの、使い方がわかりませんでした。
よろしくお願いいたします。

begin
  FileName :='test.dat';
  DataOffset := 200;
  CutLength := 16;

  Stream := TFileStream.Create(FileName, fmOpenRead);
  try
    Stream.Read(Buffer, SizeOf(Buffer));
    if ReadLength = 0 then Exit;
    for I := (DataOffset) to (DataOffset+CutLength-1) do begin
      (* 16進数で画面に出力 *)
      Edit2.Text :=Edit2.Text + (Format('%.2x ', [Buffer[I]]));
    end;
  finally
    FreeAndNil(Stream);
  end;
  //このあとでEdit1.textを元にバイナリデータを作る
end;


Nov  2013-04-13 05:27:58  No: 44305

(1)(2)は順番に実施すればOKです。各メソッドの詳細はヘルプを参照してください。

(1) DataOffsetの位置にStream.Seek。
  Seekメソッドの第1引数にDataOffsetを指定します。
  第2引数にDataOffsetの解釈方法を指定します。先頭からなのでsoBeginning。

(2) CutLength(<SizeOf(Buffer))の長さだけStream.Read。
  Readメソッドの第2引数にCutLengthを指定します。但し、Bufferサイズを超えない値に制限してください(今回は16<=256なので不要ですが)。

上記の修正コードについて、まず、(1)のSeekが足りません。
また、ReadLengthに値が代入されていません。
あと、forループで、Buffer[200〜215]を参照してますが、(1)(2)の方法で読み取った場合、
Buffer[0〜15]が、参照すべき値となります。

ちなみに、表示方法については、System.Classes.BinToHexというルーチンが使えそうです(余裕があれば)。


玲奈  2013-04-13 08:29:11  No: 44306

Novさん、ありがとうございました。

ヘルプ見ても使い方がわからくて必死にキーワードを検索して、
なんとかSeekのサンプルが見つかりました。

一応、これで切り抜くことができました、しかし、
「Readメソッドの第2引数にCutLengthを指定」をしていません。
Stream.Read(Buffer, CutLength(<SizeOf(Buffer)));  ではエラーとなり、
使い方はわかりませんでした。

  FileName :='test.dat';
  DataOffset := 200;
  CutLength := 16;
  Stream := TFileStream.Create(FileName, fmOpenRead);
  try
    Stream.Seek(DataOffset ,soBeginning);
    Stream.Read(Buffer, SizeOf(Buffer));
    if ReadLength = 0 then Exit;
    for I := 0 to 15 do begin
      Edit2.Text :=Edit2.Text + (Format('%.2x ', [Buffer[I]]));
    end;
  finally
    FreeAndNil(Stream);
  end;

CutLengthの利用方法がわからないままですが、
次はファイルに保存する部分を探してみます。
ありがとうございました。


Nov  2013-04-13 12:44:46  No: 44307

>Stream.Read(Buffer, SizeOf(Buffer));
これは、16byteしか必要ないのに256byte読み込んでます。配列を埋める意図が無いのであれば、無駄です。
また、次のif文で使用しているReadLengthの値に、有用な値が代入されてません。
もし、CutLengthが可変なら、この1行を、次のように書き換えます。

if CutLength>SizeOf(Buffer) then CutLength := SizeOf(Buffer);
ReadLength := Stream.Read(Buffer, CutLength);

ちなみにこの方法では、Bufferサイズを超えるCutLengthが設定されたとき、
必要な長さのデータが取得できませんので、留意してください。

ファイルへの保存は、TMemoryStreamとSetPointerメソッドを使うと楽かも。


玲奈  2013-04-13 16:45:08  No: 44308

Novさん
ようやく理解できました。
丁寧に説明していただき助かりました。

保存は、クラシカルな方法というのを見つけました。
しかしTMemoryStreamとSetPointer、調べてみます。

ありがとうございました。


助監督  2013-04-13 18:20:39  No: 44309

過保護なサンプルを作ってみました。TEditでなくTMemoに出力します。
かなりごちゃごちゃしてますが、エラーチェックと表示を除くと実質的にはわずかです。
もしCutLengthが可変するなら、Bufferは動的配列を使うのが便利だと思います。

>切り抜いたものを別のバイナリデータとして保存
//このあとでEdit1.textを元にバイナリデータを作る
バイナリデータはBufferにありますよね。読み込みと同じ要領でやっても簡単ですよ。

ヘルプはあまり上等ではありませんが、それでもよく読んだ方がいいです。

procedure TForm1.Button1Click(Sender: TObject);
var
  FileName : String;
  ReadLength: Longint;
  I: Integer;
  Stream: TFileStream;
  DataOffset,CutLength : Integer;  //切り抜き開始場所,切り抜く長さ
  Buffer: array of Byte;
begin
  FileName :='test.dat';
  DataOffset := 200;
  CutLength := 16;

  Setlength(Buffer, CutLength);

  Stream:=TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Memo1.Lines.Add(Format('Stream.Size=%d', [Stream.Size]));
    Memo1.Lines.Add('');

    if DataOffset>(Stream.Size-1) then begin
      Memo1.Lines.Add(Format('※DataOffsetの位置がStream.Size-1を超過※ DataOffset=%d', [DataOffset]));
    end else begin
      if (DataOffset+CutLength)>Stream.Size then begin // エラーチェックならこちらだけでも十分
        Memo1.Lines.Add(Format('※CutLengthの終端がStream.Sizeを超過※ DataOffset+CutLength=%d', [DataOffset+CutLength]));
      end;
    end;

    Stream.Seek(DataOffset, soBeginning); // Seekは(Stream.Size-1)を超過できてしまうことに注意

    ReadLength:=Stream.Read(Buffer[0], Length(Buffer)); // 第1引数は「型なしパラメータ」、ちょっと特殊
    if ReadLength<>Length(Buffer) then begin
      Memo1.Lines.Add(Format('※※CutLength分の読み取りに失敗※※  ReadLength=%d', [ReadLength]));
    end;

    for I:=0 to High(Buffer) do begin
      (* 16進数で画面に出力 *)
      Memo1.SelText:=Format('%.2x ', [Buffer[I]]);
    end;
    Memo1.Lines.Add('');
  finally
    Stream.Free;
  end;

  //切り抜いたものを別のバイナリデータとして保存

  FileName:=ChangeFileExt(FileName,'_Cut'+ExtractFileExt(FileName));

  Stream:=TFileStream.Create(FileName, fmCreate);
  try
    ReadLength:=Stream.Write(Buffer[0], Length(Buffer));
  finally
    Stream.Free;
  end;

end;


玲奈  2013-04-13 20:02:38  No: 44310

助監督さん

動的配列、そうするべきですね。

Bufferに入ってるから簡単に保存・・・全く思いつきませんでした。
Edit1から生成するつもりでした。
(あれれ、そういえばBufferは最後に解放しなくても良いのかな)

fmShareDenyWriteとか、エラーチェックの方法、そして保存方法、
まだ理解できていない点がありますが、ヘルプを見ながら理解していこうと思います。

Novさん助監督さん、大変たすかりました。
ありがとうございました。


玲奈  2013-04-13 20:03:43  No: 44311

解決チェック忘れてました


takana  2013-04-13 21:21:58  No: 44312

>動的配列、そうするべきですね。

動的配列にしなくてもできますよ。
Buffer: array of Byte;

Buffer: array[0..15] of Byte;

Setlength(Buffer, CutLength);

//Setlength(Buffer, CutLength);


助監督  2013-04-15 07:55:36  No: 44313

サンプルコード中、別のバイナリデータとして保存する箇所におかしな記述があったので訂正しておきます。
ReadLength:=Stream.Write(Buffer[0], Length(Buffer));
  ↓
Stream.Write(Buffer[0], Length(Buffer));

>(あれれ、そういえばBufferは最後に解放しなくても良いのかな)
動的配列はString型と似ていて、放っておけば勝手に破棄してくれるとのことです。
使い勝手が良いので、私は好きです。
Createしたものだけが要Free(FreeAndNil)とだけ覚えとけばだいたいOKかと。

☆Delphi 言語ガイド - データ型、変数、定数:インデックス - 構造化型(Delphi) - 3.2 動的配列
http://docwiki.embarcadero.com/RADStudio/XE3/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B#.E5.8B.95.E7.9A.84.E9.85.8D.E5.88.97

>動的配列、そうするべきですね。
>動的配列にしなくてもできますよ。
必要な要素数が固定のものは静的配列、可変のものは動的配列にするのが気分が良い…と思います。


玲奈  2013-04-15 23:55:04  No: 44314

こんにちは。
takanaさん、助監督さん、ありがとうございます。
今回は可変の可能性もあるので動的配列が良さそうです。
それから、訂正、ありがとうございました。
全く気付いていませんでした。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加