動的配列をグローバルに参照するには?

解決


枯葉マーク  2004-05-20 04:26:23  No: 9055

古い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.


jok  2004-05-20 05:19:08  No: 9056

正常に動作しますが。

-------------------------------
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.


jok  2004-05-20 05:25:03  No: 9057

> begin
>   AssignFile(F, FName);

    Reset(F,SizeOf(TIconRec));

>  Seek(F, 128);
   SetLength(IconRec, n);       //②③で
   BlockRead(F, IconRec[i], n);
>  CloseFile(F);
> end;

BlockRead() と Reset() のヘルプを見てください。


jok  2004-05-20 05:27:05  No: 9058

すみません訂正です。

× BlockRead(F, IconRec[i], n);
○ BlockRead(F, IconRec[0], n);


枯葉マーク  2004-05-20 06:38:20  No: 9059

早速のレス有難うございます。
jokさんのコード、きれいに動きました。
こちらMDI仕様にしていますので、Form2.Show;→TForm2.Create(Self);としましたが、これも問題ありませんでした。
自分のコードのどこに頓馬があるのか、よく調べてみます。
見当違いな方向で何日もさまよっていた気がします。
とりあえず感謝です。


jok  2004-05-20 08:22:15  No: 9060

> 自分のコードのどこに頓馬があるのか、よく調べてみます。

上にも書きましたけど、

>   Reset(F);
>   for i:= 0 to n-1 do             //nはレコード数
>     BlockRead(F, IconRec[i], 98);
>   CloseFile(F);

このコードだと 128*98*n バイト分のデータが読み込まれてしまいます。
したがって、配列の範囲外へアクセスすることになり、実行時にエラーになりま
す。静的配列のときは、n の値にもよりますが、たまたま範囲内で済んだので
しょう。Blockread() の第三引数についてヘルプを読んでください。


jok  2004-05-20 09:54:18  No: 9061

蛇足ですが、TFileStream を使うとややこしさが激減すると思います。


枯葉  2004-05-20 11:14:48  No: 9062

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が一番怪しいと踏んでいるのですが・・・


枯葉  2004-05-20 11:17:09  No: 9063

TFileStream、気になっていたので早速勉強します。


jok  2004-05-20 13:54:06  No: 9064

こんな感じですか。微調整してください。

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;


枯葉マーク  2004-05-21 06:22:27  No: 9065

素人の「夜だけプログラマ」なので、お返事が遅くなりました。
解決です。
なんと分かりやすいコードなんでしょう。Dlphiが好きになってしまいそうです。
ResetやBlockReadへの理解は後回しにしてでも、気持ちよく前に進めます。
jokさん、本当に有難うございました。


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

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






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