VBで作られたバイナリファイルを読み込むには?

解決


あや  2004-07-06 01:08:46  No: 9758

VBで作成されたデータ保存用ファイルの内容をDelphiで読み込もうとしているのですが、なかなかうまくいかなくて質問させていただきました。
データの内容は以下のようになっています。
Type Person
  a as string*12
  b as string*5
      ・
      ・
  e as string*8
  f(6) as single
  g(6) as single
end Type

文字列のデータは引き出せたのですが、配列型の数値の値が見当違いな値になってしまって困ってます。

type
 dat=Record
  a:array[1..12] of char;
  b:array[1..5] of char;
        ・
        ・
  e:array[1..12] of char;
  f:array[1..6] of single;
  g:array[1..6] of single;
end;
var
 data1:file of dat;
 data2:dat;
begin
 AssignFile(data1,'ファイル名');
 Reset(data1);
 Read(data1,data2);
 CloseFile(data1);
 
 showmessage(floattostr(data2.f[0]));  
end;

実際には30と出るはずが、156987E19などでたらめな値ででます。色々調べてみましたが、わからずこちらで質問さしていただきました。簡単なアドバイスでもいいので、賢明な方のご助言お願いします。


jok  2004-07-06 01:37:41  No: 9759

>   e as string*8

>   e:array[1..12] of char;

長さがあってないのでは?


ふぐちゃん  2004-07-06 01:46:41  No: 9760

> showmessage(floattostr(data2.f[0]));
f:array[1..6] of single;
と定義されていて、f[0]としているのは?


あや  2004-07-06 02:00:48  No: 9761

すみません、書き込み時にタイプミスしていました。
type
 dat=Record
  a:array[1..12] of char;
  b:array[1..5] of char;
        ・
        ・
  e:array[1..8] of char;
  f:array[1..6] of single;
  g:array[1..6] of single;
end;
var
 data1:file of dat;
 data2:dat;
begin
 AssignFile(data1,'ファイル名');
 Reset(data1);
 Read(data1,data2);
 CloseFile(data1);
 
 showmessage(floattostr(data2.f[1]));  
end;


にしの  2004-07-06 02:57:58  No: 9762

f(6) as single

ならば、

f:array[0..6] of single;

か、

f:array[1..7] of single;

では?


にしの  2004-07-06 03:18:04  No: 9763

もう1つ。
アラインメントはどうなってます?
構造体の途中が省略されていて解りませんが、Delphi側を、packed recordにしてみては?


あや  2004-07-06 17:21:28  No: 9764

にしのさん、返信ありがとうございます。

f(6) as singleの場合は配列数を7個用意しないと駄目なのですか?
また、アラインメントについては私の勉強不足でよくわかりません。アラインメント自体の意味がわかっていないので・・・。
ちなみに、packed recordにしてみましたが、値は変化しましたけど、得たい値ではありませんでした。


HOta  2004-07-06 18:00:20  No: 9765

VBの場合の配列宣言は、宣言がなければ0から始まります。
F(6)はF(0)から始まり、F(6)まででは無いでしょうか?
実際のファイルをみるか、デバッグで内容をみればよく分かると思います。


HOta  2004-07-06 18:00:26  No: 9766

VBの場合の配列宣言は、宣言がなければ0から始まります。
F(6)はF(0)から始まり、F(6)まででは無いでしょうか?
実際のファイルをみるか、デバッグで内容をみればよく分かると思います。


あや  2004-07-06 18:24:42  No: 9767

HOtaさん、返信ありがとうございます。
言われたことを調べてみたら、私の勘違いだったみたいですね。
配列数を7個にしたらきちんと動作しました!
どうもありがとうございます。

さしでがましいでしょうが、もうひとつ問題にぶつかってしまいました。
このファイルには、上述のデータが何個も入っています。1つぶんのデータを取り出すことはできましたが、2つ、3つと順番に取り出すにはどのようにすればよいでしょうか?改行などでデータが分かれていないのでどのように区別すればいいのか思案中です。


にしの  2004-07-06 19:17:29  No: 9768

単純なシーケンシャルですよね。
今はどのように読み込んでいますか?
また、どのように書き込んでいますか?

通常は、書き込み時に
Dim wPerson As Person
・・・
Open ファイル名 For Random As #1 Len = Len(wPerson)
・・・
Put#1, レコード番号(1〜), wPerson
・・・
Put#1, レコード番号(1〜), wPerson
・・・
Close #1
とします。

あとは、Delphi側の読み込みは、
var
  fs: TFileStream;
  wPerson: Person;
begin
  fs := nil;
  try
    fs := TFileStream.Create(ファイル名, fmOpenRead);
    while fs.Read(wPerson, SizeOf(Person))>0 do
    begin
    end;
  finally
    if Assigned(fs) then FreeAndNil(fs);
  end;
end;

というようにします。


にしの  2004-07-06 19:26:11  No: 9769

アラインメントは、構造体の各要素の区切りの位置です。
たとえば、
type
  TestRec1 = packed Record
    a: char;
    b: single;
    c: char;
    d: double;
  end;
  TestRec2 = Record
    a: char;
    b: single;
    c: char;
    d: double;
  end;

という定義をして、

ShowMessage(IntToStr(SizeOf(TestRec1)));
ShowMessage(IntToStr(SizeOf(TestRec2)));

としてみてください。
それぞれ、14,24と出たと思います。
これは、
    a: char;      1バイト
    b: single;    4バイト
    c: char;      1バイト
    d: double;    8バイト
ですが、packed指定があると、各要素の間を詰められるため、14となります。
packed指定がないと、ワード境界、ダブルワード境界まで隙間ができます。

a〜bが4バイト。
b〜cが4バイト。
c〜dが8バイト。

確認するには、
Memo1.Lines.Add(IntToStr(Integer(PChar(@a.b)-PChar(@a.a))));
Memo1.Lines.Add(IntToStr(Integer(PChar(@a.c)-PChar(@a.b))));
Memo1.Lines.Add(IntToStr(Integer(PChar(@a.d)-PChar(@a.c))));
というように、ポインタの差を見れば解ります。


あや  2004-07-06 22:30:35  No: 9770

にしのさん、返信ありがとうございます。
VBのほうの書き込みはにしのさんの書かれているのと同じです。
読み込みは最初にも書きましたが
type
 dat=Record
  a:array[1..12] of char;
  b:array[1..5] of char;
        ・
        ・
  e:array[1..12] of char;
  f:array[1..7] of single;
  g:array[1..7] of single;
end;
var
 data1:file of dat;
 data2:dat;
 i:integer;
begin
 AssignFile(data1,'ファイル名');
 Reset(data1);
 for i:=1 to 10 do
 begin
  Read(data1,data2);
  showmessage(floattostr(data2.f[1]));  
 end;
 CloseFile(data1);
end;
このようになっています。

1つ目は読めるのですが、1つ目のデータの終わりから2つ目のデータの始めまでに空白がかなりあります。
その空白領域を読み込む変数を構造体に追加して無理やりやってみたのですが、最後のデータにはその空白がないために、「ファイルの末尾以降を読み込みました」でエラーになります。TFileStreamなどの使い方をまだよく理解していないのでこんなやりかたになっています。

アライメントの話、ありがとうございました。
よく読んで勉強さしていただきます。


にしの  2004-07-06 23:26:05  No: 9771

Reset手続きの第2引数を省略していますが、構造体の大きさは128で良いのでしょうか。
省略すると128ですよ。


にしの  2004-07-06 23:32:23  No: 9772

失礼しました。型つきFILEでしたね。
こちらで試してみても再現しません。正しく読み込めます。
構造体のサイズは本当にあっていますか?
VB側で1つのデータだけ出力し、そのファイルサイズを調べてみてください。
次に、delphi側で、ShowMessage(IntToStr(SizeOf(Person)));などとして、構造体のサイズを調べてみてください。
同じでなければ、何かが違います。ここが同じにならなければ正しく読み込めません。


あや  2004-07-07 17:37:42  No: 9773

にしのさん、返信送れてすいません。

構造体のサイズは1つ目のデータはきちんと取り込めているのであっていると思います。ですが、1つのデータだけ出力した時のファイルサイズを調べるとVBでは構造体のサイズより何バイトか大きくでます。これが前述した空白といっていた部分です。
[1つ目のデータ]          [2つ目のデータ]          [3つ目のデータ]
このような感じです。また、この空白部分は最後のデータの後には入っていないのです。最後のデータの場合だけ、例外的に処理しようかなと考えていますが、何かいい方法あるでしょうか?


にしの  2004-07-07 19:05:36  No: 9774

出力の時、
Open ファイル名 For Random As #1 Len=構造体のサイズ
としているんですよね?
# Lenが肝です
1つめのデータが取れているのは、開始位置が0で同じだからです。
2つめのデータでずれていくと言うことは、出力サイズと取得サイズが違うからです。
これでは合っているとは思えません。
まずは、先に投稿したとおり、構造体のサイズを合わせてください。
合っていれば、そんなに難しい物ではありません。

隙間がほしいのであれば、構造体自体に隙間分の変数(ダミーの)を用意するべきです。


jok  2004-07-07 19:28:10  No: 9775

>この空白部分は最後のデータの後には入っていないのです。

構造体のサイズがあってないんじゃなくて、本当に隙間なのでは?


にしの  2004-07-07 19:42:13  No: 9776

隙間であれば、明示的に出力した方がよいですね。
たぶんですが、OpenのときにLenが指定されておらず、たとえば
Len=10, 構造体のサイズ5のとき、

ABCDE_____FGHIJ
# _が隙間
というように、構造体のサイズは正しいが、次レコードとの間に隙間が発生しているということだと思います。

ちゃんとサイズ確認しました?
2レコード出力して、1レコードの倍のサイズになってます?
こういうときは、出来ることから確認しないと。


あや  2004-07-08 23:35:23  No: 9777

あれから教えていただいたことを参考にやってみたところ、なんとか最後のデータ以外は取り出せるようになりました。なぜか最後のデータだけ、隙間の部分がないために、構造体のサイズが異なり取り出せません。
今回は、プログラムではなく別の方法でなんとかすることになったのでこれで解決とさせていただきます。
ご教授してくださいましたみなさま、ありがとうございました。


あや  2004-07-08 23:35:52  No: 9778

チェック忘れてました


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

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






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