こんばんは。
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;
whileループは必要ありません。
(1) DataOffsetの位置にStream.Seek。
(2) CutLength(<SizeOf(Buffer))の長さだけSteam.Read。
表示方法は色々ありますが、配列の添字が定義サイズを超えてはいけません。
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;
(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というルーチンが使えそうです(余裕があれば)。
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の利用方法がわからないままですが、
次はファイルに保存する部分を探してみます。
ありがとうございました。
>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メソッドを使うと楽かも。
Novさん
ようやく理解できました。
丁寧に説明していただき助かりました。
保存は、クラシカルな方法というのを見つけました。
しかしTMemoryStreamとSetPointer、調べてみます。
ありがとうございました。
過保護なサンプルを作ってみました。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;
助監督さん
動的配列、そうするべきですね。
Bufferに入ってるから簡単に保存・・・全く思いつきませんでした。
Edit1から生成するつもりでした。
(あれれ、そういえばBufferは最後に解放しなくても良いのかな)
fmShareDenyWriteとか、エラーチェックの方法、そして保存方法、
まだ理解できていない点がありますが、ヘルプを見ながら理解していこうと思います。
Novさん助監督さん、大変たすかりました。
ありがとうございました。
解決チェック忘れてました
>動的配列、そうするべきですね。
動的配列にしなくてもできますよ。
Buffer: array of Byte;
↓
Buffer: array[0..15] of Byte;
Setlength(Buffer, CutLength);
↓
//Setlength(Buffer, CutLength);
サンプルコード中、別のバイナリデータとして保存する箇所におかしな記述があったので訂正しておきます。
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
>動的配列、そうするべきですね。
>動的配列にしなくてもできますよ。
必要な要素数が固定のものは静的配列、可変のものは動的配列にするのが気分が良い…と思います。
こんにちは。
takanaさん、助監督さん、ありがとうございます。
今回は可変の可能性もあるので動的配列が良さそうです。
それから、訂正、ありがとうございました。
全く気付いていませんでした。
ツイート | ![]() |