古いVBソースの移植を目指しているDelphi初心者です。
バイナリーファイルからレコードを読み込み、動的配列に入れて複数のユニットから参照したいのですが、うまくいきません。
①interface部で静的配列を宣言すれば正常に動きますが、レコード数が可変なので使えません。
②動的配列に変えて実行部でSetLengthすると、コンパイルはできますが実行時エラーで叱られました。
③varとSetLengthを両方実行部に置くと、他のユニットで未定義変数だとまた叱られました。
AllocMem方式に変えて何とか凌いではみたものの、どうも釈然としません。
つまらない勘違いをしている気がします。どなたかご教示くださいませんでしょうか?
コードの一部を記載します。
unit Unit1;
interface
type
TIconRec = packed record
Num: SmallInt;
Name1: array[0..63] of Char;
Name2: array[0..15] of Char;
Icon: array [0..7] of SmallInt;
end;
var
IconRec = array[0..999] of TIconRec; //①静的配列の場合
//IconRec = array of TIconRec; //②動的配列で
implementation
uses Unit2;
{$R *.dfm}
procedure IconRead;
var
i: integer;
//IconRec = array of TIconRec; //③動的配列で
begin
AssignFile(F, FName);
Reset(F);
Seek(F, 128);
//SetLength(IconRec, n); //②③で
for i:= 0 to n-1 do //nはレコード数
BlockRead(F, IconRec[i], 98);
CloseFile(F);
end;
end.
正常に動作しますが。
-------------------------------
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
IntArray: array of Integer;
implementation
{$R *.DFM}
uses
Unit2;
procedure TForm1.Button1Click(Sender: TObject);
var
i:integer;
begin
SetLength(IntArray,100);
for i := 0 to 99 do
IntArray[i] := 100-i;
Form2.Show;
end;
end.
-------------------------------
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm2 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form2: TForm2;
implementation
{$R *.DFM}
uses
Unit1;
procedure TForm2.Button1Click(Sender: TObject);
var
i:integer;
begin
for i := 0 to High(IntArray) do
ListBox1.Items.Add(IntToStr(IntArray[i]));
end;
end.
> begin
> AssignFile(F, FName);
Reset(F,SizeOf(TIconRec));
> Seek(F, 128);
SetLength(IconRec, n); //②③で
BlockRead(F, IconRec[i], n);
> CloseFile(F);
> end;
BlockRead() と Reset() のヘルプを見てください。
すみません訂正です。
× BlockRead(F, IconRec[i], n);
○ BlockRead(F, IconRec[0], n);
早速のレス有難うございます。
jokさんのコード、きれいに動きました。
こちらMDI仕様にしていますので、Form2.Show;→TForm2.Create(Self);としましたが、これも問題ありませんでした。
自分のコードのどこに頓馬があるのか、よく調べてみます。
見当違いな方向で何日もさまよっていた気がします。
とりあえず感謝です。
> 自分のコードのどこに頓馬があるのか、よく調べてみます。
上にも書きましたけど、
> Reset(F);
> for i:= 0 to n-1 do //nはレコード数
> BlockRead(F, IconRec[i], 98);
> CloseFile(F);
このコードだと 128*98*n バイト分のデータが読み込まれてしまいます。
したがって、配列の範囲外へアクセスすることになり、実行時にエラーになりま
す。静的配列のときは、n の値にもよりますが、たまたま範囲内で済んだので
しょう。Blockread() の第三引数についてヘルプを読んでください。
蛇足ですが、TFileStream を使うとややこしさが激減すると思います。
Jokさんのおっしゃる辺りに問題がありそうだ、ということは分かってきました。
まだ解決していません。自分にはファイル入出力の根本が理解できていないようです。ずるずると甘えたくはないのですが、振舞い方のヒントをちょうだいできれば有難いです。
読みたいファイルは次のような構造になっています。
すべて1バイトずつで制御すべきかなと思っています。
(空)18バイト
ItemRec 40バイト 全体定義レコード
(空)70バイト
IconRec[0] 98バイト 項目定義レコード
Label[0..m0] 32バイト×m0個 細目ラベル
IconRec[1] 98バイト
Label[0..m1]
・・・・・・
IconRec[n] 98バイト
Label[0..mn]
−−−−−− コード(部分)−−−−−−−−−−−−−
var
F: File of Byte; //型つきファイルを宣言しておいて
ItemRec: TItemRec; //40バイトのレコード
IconRec: array of TIconRec; //98バイトのレコード
procedure IconRead;
var
i, j: integer;
Label: array[0..35] of char //36バイトの文字配列
begin
AssignFile(F, FName);
Reset(F); //1バイト単位のつもり
暗黙128バイトだったらとても困る
Seek(F, 18); //先頭から19バイト目のつもり
BlockRead(F, ItemRec, 40); //40バイト1レコードのつもり
Seek(F, 128); //先頭から129バイト目のつもり
SetLength(IconRec, ItemRec.n); //ItemRec.n個のIconRec分(怪しい!)
for i:= 0 to ItemRec.n-1 do
begin
BlockRead(F, IconRec[i], 98); //98バイトのレコード1個ずつのつもり
for j:= 0 to IconRec.m-1 do //IconRec.m個分
BlockRead(F, Label[J], 36); //Label1個ずつ読み飛ばしのつもり
end;
CloseFile(F);
end;
難所を越えたいと四苦八苦しています。
SetLengthが一番怪しいと踏んでいるのですが・・・
TFileStream、気になっていたので早速勉強します。
こんな感じですか。微調整してください。
procedure TForm1.Button1Click(Sender: TObject);
var
fs:TFileStream;
i: integer;
begin
fs := TFileStream.Create('c:\Test.dat',fmOpenRead or fmShareDenyWrite);
try
fs.Seek(18,soFromBeginning);
fs.Read(ItemRec,SizeOf(TItemRec));
fs.Seek(70,soFromCurrent);
SetLength(IconRec,ItemRec.n);
for i := 0 to ItemRec.n-1 do begin
fs.Read(IconRec[i],SizeOf(TIconRec));
fs.Seek(32*IconRec[i].m,soFromCurrent);// 32byte(?) x IconRec[i].m 分前進シーク
end;
finally
fs.Free;
end;
end;
素人の「夜だけプログラマ」なので、お返事が遅くなりました。
解決です。
なんと分かりやすいコードなんでしょう。Dlphiが好きになってしまいそうです。
ResetやBlockReadへの理解は後回しにしてでも、気持ちよく前に進めます。
jokさん、本当に有難うございました。
ツイート | ![]() |