アーカイブファイル内のCSVデータ読み込みについて

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
シエル

アーカイブファイル内のCSVデータ読み込みについて

#1

投稿記事 by シエル » 15年前

今日2回目の質問になります。申し訳ありません。

昨日自作のアーカイバが完成したので、
そのアーカイバを使って作成したアーカイブファイルからデータを読み込もうとしています。
画像データや音声データは試行錯誤の上、何とか読み込めて表示や再生できるようになりましたが、
敵出現データのCSVファイルの読み込み方法について悩んでおります。

アーカイブ化する前は、直接ファイル名を指定して、fopenで読み込んだあと、
fgetsでcsvファイルのヘッダ部分を一行読み飛ばし、
fscanfで下記のように読み込んでいました。

fscanf(fp,"%d,%d,%d,・・・・省略)

ですが、アーカイブファイルの場合はバイナリファイルとしてアーカイブファイル全体を読み込んでいるので
ファイル名を指定して読み込むfopenが使えず、fscanfも使えません。

sscanfという関数があるのも見つけましたが、ヘッダ部分をどう読み飛ばすか、
また、どの部分までがそのCSVデータと見なすのかという指定ができそうにありません。

このようなアーカイブファイル内のCSVデータの読み込み方法として、
便利な方法はないでしょうか?

ご教授の程、よろしくお願い致します!

softya

Re:アーカイブファイル内のCSVデータ読み込みについて

#2

投稿記事 by softya » 15年前

私の場合ゲーム用のアーカイブファイルは、利便性の都合上ファイル毎に個別圧縮してました。
あとはfopenやfgetsの代わりをするストリーム関数を作って解凍しながら処理に受け渡すことでfopen時と変わらない使い勝手が実現できます。sscanfは、fgetsの代わりの関数で読み込んだデータを処理することでfscanf風に使えますね。

palladium

Re:アーカイブファイル内のCSVデータ読み込みについて

#3

投稿記事 by palladium » 15年前

開発環境等の明記がありませんが、どのような環境でも可能な方法をお探しでしょうか?

シエル

Re:アーカイブファイル内のCSVデータ読み込みについて

#4

投稿記事 by シエル » 15年前

>>softyaさん。ありがとうございます。

自作のストリーム関数ですか。どう作ればいいかさっぱりです。
CSVファイルのために個別圧縮ですか。出来ればまとめたいですね。。。


>>palladiumさん。ありがとうございます。

すいません。
コンパイラはVC+2008EEで、Windows7を使っています。
とりあえずWindows環境で実現できれば良いです。

palladium

Re:アーカイブファイル内のCSVデータ読み込みについて

#5

投稿記事 by palladium » 15年前

実装がわからないのでなんとも言えませんが
GetFileSize で元ファイルのサイズを取得
CreateFile でファイルにアクセス
SetFilePointer で読み込み位置を指定
ReadFile で読み込み
とかイメージします。

しかし、アプリケーションデータならXMLを先に考えます。

シエル

Re:アーカイブファイル内のCSVデータ読み込みについて

#6

投稿記事 by シエル » 15年前

>palladiumさん。ありがとうございます。

その方法とほぼ同じ方法をfseek等でやろうとしました。
ですが、読み込もうとしているCSVファイルはデータの項目のヘッダが一行目にあります。
それを読み飛ばすにはどうすればよいでしょうか?


追記:
すいません。「実装」とおっしゃってるのが何なのかいまいちイメージできないんですが、
一応現在はシューティングゲームを作成しておりまして、その敵の出現データを管理しているのが
そのCSVデータになります。 画像

softya

Re:アーカイブファイル内のCSVデータ読み込みについて

#7

投稿記事 by softya » 15年前

私の話の例は、個別に圧縮した上でアーカイブしてあるので、アーカイブされてますよ。圧縮が個別に掛かっているだけです。
今回の場合にはシエルさんが作ったと言っているアーカイバのデータ構造の簡単な説明しないとみなさんアイデアが出しづらいと思います。

>ですが、読み込もうとしているCSVファイルはデータの項目のヘッダが一行目にあります。
>それを読み飛ばすにはどうすればよいでしょうか?

上にも書きましたがその一行目が圧縮されているのかとか、どうやってファイル先頭にfseekできるのかとか、一行目を区別する改行コードはどうなっているとか、細かい説明が欲しいですね。

>すいません。「実装」とおっしゃってるのが何なのかいまいちイメージできないんですが、
この場合の実装は、どういうデータ構造やプログラムになっているか分からないと言う意味だと思います。

palladium

Re:アーカイブファイル内のCSVデータ読み込みについて

#8

投稿記事 by palladium » 15年前

>「実装」とおっしゃってるのが何なのかいまいちイメージできない
アーカイブファイルのデータ構造です。

シエル

Re:アーカイブファイル内のCSVデータ読み込みについて

#9

投稿記事 by シエル » 15年前

そうですね。説明が足りなかったかもしれません。

アーカイバのデータ構造としては、まず最初にファイルの情報を保存したヘッダを書き込んでいます。
そのヘッダの内容として主なものは下記の三つです。ほかにも幾つか情報は書き込んでますが。

・ファイル名
・サイズ
・自分の先頭からの位置(バイト)

ですので、まずアーカイブのヘッダさえ読み込めれば、CSVファイルがアーカイブファイルの中のどの位置にあって、サイズはどれくらいかが得られます。

後はそのヘッダの後に、順番に各ファイルのデータをバイト単位で書き込んでいっているだけの単純なものです。
なので、圧縮等はしてません。(いずれ圧縮や暗号もやりますけど)

データ構造はこんな感じです。アーカイバは初めて作ったので説明が足りなかったらすみません。


こういう構造ですので、CSVファイルがある先頭のポインタまでは取得できるんですが、
そこからどうやって、一行読み飛ばして、カンマごとにデータを読んでいけばいいのかが分かりません。
テキストモードで読みこんでいれば余裕なんですけど、バイナリですので。。。


説明は大体以上になります。ご教授の程、よろしくお願い致します。

palladium

Re:アーカイブファイル内のCSVデータ読み込みについて

#10

投稿記事 by palladium » 15年前

改行文字チェックではダメですか?
1行目の正体が不明なのでなんとも言えませんが。

シエル

Re:アーカイブファイル内のCSVデータ読み込みについて

#11

投稿記事 by シエル » 15年前

すいません。
一行目はその敵データを示す項目名です。
例えば、
敵イメージ、敵ショットイメージ、アイテム、等という項目がまず一行目にあって、
そのしたのその項目が示すデータがずらーっと並んでいる感じです。

softya

Re:アーカイブファイル内のCSVデータ読み込みについて

#12

投稿記事 by softya » 15年前

とりあえず簡単なのは一文字づつ読み込むしか無いです。改行コードが来れば行の終わりです。
この場合の文字コードの種別が指定されていないのですが、UNICODEだと少し説明がややこしくなります。

シエル

Re:アーカイブファイル内のCSVデータ読み込みについて

#13

投稿記事 by シエル » 15年前

やっぱり一文字ずつしかないですかね。
ちょっとやってみます。無理そうならまた書き込みます。

シエル

Re:アーカイブファイル内のCSVデータ読み込みについて

#14

投稿記事 by シエル » 15年前

うぅ・・。すみません。

何とか一行目ヘッダ部分の改行まで読み飛ばすことができ、
ポインタも二行目の最初の位置にまで持ってこれました。

が、しかし、肝心のカンマごとに区切られてるデータをどう読み込めばいいのか悩んでいます。
また一文字ずつ読んでいってカンマ部分までポインタを進めることはできますが、
肝心のデータは桁が決まっていません。
三桁だったり一桁だったり4桁だったりします。
読み込んでる文字は1バイトずつですので、これをどう元の数字に変換すればいいか悩んでいます。

思いついた方法としては、3バイト読んでカンマがあったら、
3バイト前の値を100倍、2バイト前の値を10倍、1バイト前はそのままにして、足し算して元の値を導き出す
ということしか思いつきません。

何か効率の良い方法はないでしょうか?

softya

Re:アーカイブファイル内のCSVデータ読み込みについて

#15

投稿記事 by softya » 15年前

文字列バッファを用意して改行までのデータ一文字づつ読んで文字列バッファに貯めてください。
改行が来たら、\0を文字列バッファに書き込みます。
これで一行分の文字列が出来たので、これをsscanfします。

シエル

Re:アーカイブファイル内のCSVデータ読み込みについて

#16

投稿記事 by シエル » 15年前

おお!私にとっては革命的な発想ですね!
どうやって文字列化するかが、あんまり分からないですけど、
ちょっとググってやってみます。ありがとうございます!

シエル

Re:アーカイブファイル内のCSVデータ読み込みについて

#17

投稿記事 by シエル » 15年前

すいません。文字列バッファに貯める方法がよく分かりません。

現在読み込んでいるバイナリファイルの現在位置のポインタがpだとしたら、

buf[200];

for(int i=0;i<200;++i){
buf=*p
++p;
}

こんな感じでいいのでしょうか。
この方法で文字列を作ろうとした場合、このバッファをsscanfにかけると、
block type is validのエラーが発生してしまいます。

※このソースはあくまで例です。実際はこんなのじゃありません。ちゃんと\0終端文字とか入れてます。 画像

palladium

Re:アーカイブファイル内のCSVデータ読み込みについて

#18

投稿記事 by palladium » 15年前

データを構造体にすると読み書きが容易です。

ヘボサンプルコード
/*------------------------------------------------------------------------------------------------

    Module :  StructIO.c -- 2010/08/18

    Comment:  WindowsXP sp3, 
              Visual Studio 2005 sp2, 
              Win32-Console, 
              /D "_UNICODE" /D "UNICODE"

 ------------------------------------------------------------------------------------------------*/
#include <windows.h>
#include <stdio.h>

#define RECORD  5

#pragma pack(push, 4)
typedef struct tag_SOMEDATA{
    int    RecordNumber;
    int    IntData;
    double DoubleData;
    float  FloatData;
} SOMEDATA;
#pragma pack(pop)

void MakeData(SOMEDATA*);
void Display(SOMEDATA*);
void DoWriteFile(LPTSTR, PVOID, DWORD);
void DoReadFile(LPTSTR, PVOID, DWORD);

int main(void)
{
    SOMEDATA SomeData[RECORD];
    SOMEDATA ReadData[RECORD];

    LPTSTR FileName = TEXT("SomeData.dat");
  
    printf("-------------------- Write Data ----------------------\n");
    MakeData(SomeData);
    Display(SomeData);
    DoWriteFile(FileName, SomeData, sizeof(SomeData));

    printf("-------------------- Read Data -----------------------\n");
    DoReadFile(FileName, ReadData, sizeof(ReadData));
    Display(ReadData);

    system("pause");
    return 0;
}

void MakeData(SOMEDATA *SomeData) // でたらめなデータ ^^;
{
    int i;
    for (i=0; i<RECORD; i++)
    {
        SomeData.RecordNumber = i;
        SomeData.IntData      = i * 100;
        SomeData.DoubleData   = i / 2.1;
        SomeData.FloatData    = i * 3.14f;
    }
}

void Display(SOMEDATA *SomeData)
{
    int i;
    for (i=0; i<RECORD; i++)
    {
        printf("RecordNumber : %d\n",   SomeData.RecordNumber);
        printf("IntData      : %d\n",   SomeData.IntData);
        printf("DoubleData   : %f\n",   SomeData.DoubleData);
        printf("FloatData    : %f\n\n", SomeData.FloatData);
    }
}

void DoWriteFile(LPTSTR FileName, PVOID SomeData, DWORD Size)
{
    HANDLE hFile;
    DWORD  Written;

    hFile = CreateFile(
        FileName, 
        GENERIC_WRITE, 
        (DWORD) 0, 
        (LPSECURITY_ATTRIBUTES) NULL, 
        CREATE_ALWAYS, 
        FILE_ATTRIBUTE_NORMAL, 
        (HANDLE) NULL);

    WriteFile(hFile, SomeData, Size, &Written, NULL);
    CloseHandle(hFile);
}

void DoReadFile(LPTSTR FileName, PVOID ReadData, DWORD Size)
{
    HANDLE hFile;
    DWORD  Read;

    hFile = CreateFile(
        FileName,
        GENERIC_READ,
        (DWORD) 0,
        (LPSECURITY_ATTRIBUTES) NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        (HANDLE) NULL);
    
    ReadFile(hFile, ReadData, Size, &Read, NULL);
    CloseHandle(hFile);
}

softya

Re:アーカイブファイル内のCSVデータ読み込みについて

#19

投稿記事 by softya » 15年前

>こんな感じでいいのでしょうか。
>この方法で文字列を作ろうとした場合、このバッファをsscanfにかけると、
>block type is validのエラーが発生してしまいます。

ちゃんと中身が出来ているかデバッガで確認しましたか?

シエル

Re:アーカイブファイル内のCSVデータ読み込みについて

#20

投稿記事 by シエル » 15年前

悩むこと数時間。まったく解決しない。。。ちょっと進んだけど。
まず、バイナリモードで読み込んだときとテキストモードで読み込んだときの違いを理解しました。
Windows環境では、改行は0x0d0aで表されていて、これをテキストモードで読み込んだ場合は、
C側で改行を表す0a(10)で表される\nに自動的に変換してくれる。

バイナリモードで読み込むと変換せずに読み込まれるので、そのまま0x0d0aの値まま読み込まれる。
つまり、コードを書く際は\r(0x0dが来たら、改行が来たとみなす。
しかしその1バイト先に\n(0x0a)があるので、次の行を読み込むときには、
ポインタを2バイト進めてから読み込み始める。

0x0d(\r)はcarriage return(先頭に戻る)、0x0a(\n)はLine feed(改行)を表している。

ということまで理解しました。間違ってたらご指摘お願いします。

ということを踏まえたうえで作ったコードが以下です。

for(s=0;s<200;++s){
 if(!memcmp("\r",p,sizeof(char))){
  p+=2;//この一バイト先に\nがあるので、次に読み込む文字は2バイト先なので2足す
  sbuf='\0';//最後に終端文字を設定
  break;
}
sbuf=*p;//文字列バッファへ格納。
++p;//ポインタを進める
}

if(s==200){
MessageBox(hwnd,L"200バイト以内に改行が見つかりませんでした。",L"エラー発生",MB_OK);
return;
}

if(16!=sscanf(sbuf,"%d,%d,%d,%d,%lf,%lf,%lf,%lf,%d,%d,%d,%d,%d,%d,%d,%d",・・・・省略);


このように実行すると、block type is validとエラーが発生してしまいます。
ご教授お願い致します!


>palladiumさん。ありがとうございます。参考にさせていただきます。


>softyaさん

デバッガで確認しています。
デバッガで確認する限りは、ちゃんと文字は入っています。
バッファの中一つ一つ確認しました。でもエラーが出てしまいます。


追記:画面貼っておきます
画像

softya

Re:アーカイブファイル内のCSVデータ読み込みについて

#21

投稿記事 by softya » 15年前

まず読み込みの問題なのか、別の問題なの切り分けましょう。
char *sbuf="300,140,80,640,50,10,1,3,4,1,1,1,8,3,1,1";
sscanf(sbuf,"%d,%d,%d,%d,%lf,%lf,%lf,%lf,%d,%d,%d,%d,%d,%d,%d,%d",・・・・省略);
とやってもエラーが出ますか?

シエル

Re:アーカイブファイル内のCSVデータ読み込みについて

#22

投稿記事 by シエル » 15年前

softyaさん。毎度すみません。ありがとうございます!

提示していたいだコードでやると、うまく読み込めますね。
何が違うのかが、分かりそうで分かりません。文字列リテラル?

MNS

Re:アーカイブファイル内のCSVデータ読み込みについて

#23

投稿記事 by MNS » 15年前

エラー"Block type is valid"が気になりますね。
どういう意味なんでしょうか、稀なエラーのようですが。

プログラムが何かを破壊(?)しようとしていて、
それにブロックが働いているということでしょうか?

と、すると、ポインタがおかしなところを参照しているとか…

softya

Re:アーカイブファイル内のCSVデータ読み込みについて

#24

投稿記事 by softya » 15年前

なにかが違うはずですので、リテラルとstrcmpで比較してみるとか、両方OutputDebugString()してみるとか、自分でリテラルとsbufを比較してみるとかしてみてください。なにか問題があるはずですからね。
ここで知恵を絞るとプログラミングで良い経験を積むことが出来ますよ。

palladium

Re:アーカイブファイル内のCSVデータ読み込みについて

#25

投稿記事 by palladium » 15年前

ご提示されたコードには変数の型が明記されていませんが
どのような型であっても成立する方法をお探しなのですか?

サンプルコードがはっきりしないのでわかりませんが
バイトを扱うときはchar型ではNG
BYTE型(unsigned char)ではOKという場面もあります。

もし可能であれば問題箇所を関数化して提示していただけると
こちらでも検証可能となります。

シエル

Re:アーカイブファイル内のCSVデータ読み込みについて

#26

投稿記事 by シエル » 15年前

うぅ・・・。。もう駄目。。死にそう。。。
やっと原因を突き止めることができました!

大きく分けて原因は二つありました。

一つ目は下記のコードで、ポインタpにを2バイト進めるところです。
一番最後の行まで読んだら、当然ポインタpが示す先にはもうデータはないのに、
2つポインタを進めてしまったことがまず一つ。

for(s=0;s<200;++s){
 if(!memcmp("\r",p,sizeof(char))){
  p+=2;//この一バイト先に\nがあるので、次に読み込む文字は2バイト先なので2足す
  sbuf='\0';//最後に終端文字を設定
  break;
}


二つ目は、このコードは提示してなかったんですが、
このコードの下に動的確保したポインタを解放させるコードを書いていたんですが、
私のコードは上記のように、直接ポインタの値を変更してしまってました。
その状態で、

delete [/url] p;

とやっちゃってました。当然ポインタの位置が変わってるのにこんなことしたら、
関係ない部分のメモリが解放されてえらいことですよね。
たぶんこれが、block type is validのエラーの直接的な原因でしょう。

なので、この二つの問題を解決する方法として、ポインタ変数を使って直接ポインタを進めるのではなく、
ポインタをどれだけの数進めたかを保存する変数を別で用意して対応しました。

ということで、その変数をtmpとすると、下記のコードは

for(s=0;s<200;++s){
 if(!memcmp("\r",&p[tmp],sizeof(char))){
  tmp+=2;//この一バイト先に\nがあるので、次に読み込む文字は2バイト先なので2足す
  sbuf='\0';//最後に終端文字を設定
  break;
}
sbuf=p[tmp];//文字列バッファへ格納。
++tmp;//ポインタ数管理変数を進める
}

というようになります。
これで、最終行に言った場合でも、直接ポインタ変数をいじってるわけでもないので、変なアドレスに
直接アクセスすることもないし、解放するときもポインタ変数自体は進めてないので、
delete [/url] p;で問題なく解放できます。

この問題の解決に至ったのは、皆さんのアドバイスのおかげですが、
特に大きかったのはsoftyaさんが教えてくださったOutputDebugStringです。
まったくこの存在を知らなかったのですぐググって、使用法を調べました。
この関数は私がデバッグを行ううえでかなり革命的な方法でした。
いつもはprintfなどを使ったりだとか、Windowsアプリだとメッセージボックスを出すとかしてたので、
かなり面倒臭かったんですが、この関数でこれからはだいぶデバッグが楽になりそうです。
ついでに、この関数を改良してマクロをつくり、printfのように使うという方法がネットに
載っていたので早速実装しました。
可変長引数?の宣言方法や、__VA_ARGS__マクロの存在を知ることも出来まして、
非常に勉強になりました。

後はMNSさんの「何かを破壊しようとしている」という発言です。
色々問題点を絞り込んでいる中で、完全にsscanfの使い方とか文字列バッファの代入が上手くいってないのか
というところばかりに目が行ってしまっていて、まさかメモリの解放部分が直接的な原因だとは
考えもしませんでした。
「破壊」という言葉を聞いて、まさかと思って調べたらまさにそこが原因だったわけです。

この二つのアドバイスは本当に非常に助かりました!本当にありがとうございました!

もちろんpalladiumさんのアドバイス、サンプルソースの提示も非常に参考になりました。
今後のプログラミングの参考材料にさせていただきます。

今回のエラーは今までで一番やっかいなエラーだったと言っていいと思います。
ですが、途中で諦めたりしなかったのは皆さんのアドバイスのおかげです。本当にありがとうございました!

これからもご迷惑をおかけすると思いますが、よろしくお願い致します!

とりあえず、本当に倒れそうなのでちょっと休みます。。。。

palladium

Re:アーカイブファイル内のCSVデータ読み込みについて

#27

投稿記事 by palladium » 15年前

解決おめでとうございます。
お疲れ様でした。

こちらこそ今後ともよろしくお願いします。

シエル

Re:アーカイブファイル内のCSVデータ読み込みについて

#28

投稿記事 by シエル » 15年前

>>palladiumさん。

はい。よろしくお願いします^^
char型を使ってしまっていたので、BYTE型を使うようにします。
以前から気にはしていたんですけどね;
ありがとうございます。

閉鎖

“C言語何でも質問掲示板” へ戻る