bulleye
Goto Top

Lesen binärer Dateien in array

Hallo

Ich bin gerade dabei ein kleines Programm zu schreiben. Dabei muss (für ein 3D-Spiel) ein Array vom Typ folgender Struktur aus einer Datei gelesen werden:

struct Vertex3d
{
FLOAT x, y, z;
DWORD color;
};

Nun habe ich das einlesen mit verschiedenen Varianten getestet:

fread - liest leider keinen Text ein, obwohl keine Fehler angezeigt werden.
ifstream::get - liest leider nur zeichen ein, aber wandeld diese nicht in ein Float-Wert um

Allerdings, wie man lesen kann face-smile, scheint keine Funktion richtig das zu tun, was ich möchte, denn keine Dieser Funktionen liefert einen Wert zurück.

Hat jemand eine Lösung für dieses Problem?

Danke im vorraus.

Content-Key: 118004

Url: https://administrator.de/contentid/118004

Printed on: April 19, 2024 at 23:04 o'clock

Member: filippg
filippg Jun 11, 2009 at 14:45:16 (UTC)
Goto Top
Hallo,

fehlt dir vielleicht ein reinterpret_cast?
Mein letztes c++ ist schon wieder ein weilchen her. Aber Serialisierung von komplexen Datenstrukturen geht mit einem ostream (öffnen mit ios::binary|ios::trunc empfehlenswert) ziemlich gut.
Schreiben dann mit
s.write(reinterpret_cast<char *>(&x),sizeof(x));
und einlesen mit
s.read(reinterpret_cast<char *>(&x), sizeof(x));

Gruß

Filipp
Member: Bulleye
Bulleye Jun 11, 2009 at 15:12:58 (UTC)
Goto Top
Danke erstemal.

Bis hier hin hab ich das verstanden.
Allerdings bekomme ich bei ostream schon beim deklarieren des streams( ostream pFile; ) einen fehler:

'std::basic_ostream<_Elem,_Traits>': Kein geeigneter Standardkonstruktor verfügbar

Demnach kann ich auch nicht testen, ob das mit den reinterpret_cast funktioniert.
Aber was mich schon jetz fragwürdig macht ist, wie ich die eingelesene Zeichenkette(char *) dann in ein float wert, geschweigendenn in einen DWORD wert umwandeln soll.

(Ich verwende MS VisualCpp 2008 falls das ausschlaggebend ist.)
Member: filippg
filippg Jun 11, 2009 at 15:43:25 (UTC)
Goto Top
Hallo,

wenn du nicht postest, wie du deinen stream deklarierst kann man auch schlecht sagen, wo da der Fehler sein könnte.
Aber ich habe einen Fehler bei meinem Post entdeckt: ich verwende oFstream, nicht ostream.
Habe mal ein altes Beispiel ausgekramt.

	void FilterMain::serialize(SimObjekte& so, char* dateiname){
		//Datei zur Serilaisierung öffen/anlegen
		SerializeStream ostream(dateiname, ios::binary|ios::trunc);
		if(!ostream)
			throw SchUSi::FileNotFound(string("Die Datei ")+dateiname+" konnte nicht zum Speichern geoeffnet werden.");  
		/** Config::Dateiformatversion: Bei jeder Änderung am Dateiformat sollte diese
			Konstante auf einen anderen Wert gesetzt werden, Dateien im alten Format
			(was beim Einlesen Fehler erzeugen würde) können dann nicht mehr
			geöffnet werden */
		ostream.write(reinterpret_cast<char *>(&(Config::Dateiformatversion)),sizeof(Config::Dateiformatversion));
		SimObjekt::Realtyp rt;
		for (unsigned int i = 0; i < so.size(); i++) {				   //iterieren über alle SimObjekte
			rt = (*so[i]).getRealtyp();
			ostream.write(reinterpret_cast<char *>(&rt),sizeof(rt));   //erst speichern, um was für ein Objekt es sich gerade handelt (Realtyp)
			(*so[i]).serialize(ostream);                               //dann Objekt selber
		}
		ostream.close();                                               //Serilisierung fertig, Datei schliessen.
	}
Wobei in einer entsprechenden Header-Datei
typedef std::ofstream SerializeStream;
typedef std::ifstream DeserializeStream;
vorgenommen ist. SimObject: abstrakte Basisklasse der zu serialisierenden Klassen. Realtyp: SimObjekt hat mehrere abgeleitete Klassen. Beim Deserialisieren muss man aber wissen, von welcher konkreten das kommende Objekt ist. Dazu bekommt einfach jedes konkrete Basisklasse eine Int-Konstante zugewiesen, die steht vor den eigentlichen Objektdaten im Stream und zeigt somit, wie die kommenden Daten zu behandeln sind. Dateiformat ist ein int.
Wieder einlesen kann man das ganze dann mit
	void FilterMain::deserialize(SimObjekte& so, const char* dateiname){
		//Datei zu Deserialisierung öffnen
		DeserializeStream istream(dateiname, ios::binary|ios::in);
		if(!istream)
			throw SchUSi::FileNotFound(string("Die Datei ")+dateiname+" konnte nicht zur Einlesen geoeffnet werden.");  
		SimObjekt* simO;

		/** Config::Dateiformatversion: Bei jeder Änderung am Dateiformat sollte diese
			Konstante auf einen anderen Wert gesetzt werden, Dateien im alten Format
			(was beim Einlesen Fehler erzeugen würde) können dann nicht mehr
			geöffnet werden */
		int DateiformatVersion;
		istream.read(reinterpret_cast<char *>(&DateiformatVersion), sizeof(DateiformatVersion));
		if (DateiformatVersion != Config::Dateiformatversion) {
        	throw SchUSi::StdError("Die verwendete Datei besitzt das falsche Format, bitte erneut erzeugen.");      
		}

		while (istream) {  
// hier wurde dann bis zum Ende des Streams gelesen. Dabei kommt immer erst der oben geschrieben RealTyp,
// passend zu diesem wird dann ein leeres Objekt dieses Typs erzeugt, dessen Deserialisierungsmethode
// aufgerufen (istream übergeben). Das Objekt liest dann seine Attributwerte aus dem Stream.
}

Gruß

Filipp
Member: filippg
filippg Jun 11, 2009 at 16:04:55 (UTC)
Goto Top
Zu reinterpret_cast nochmal: Der ist genau genommen teufelszeug weil er die Verwandlung von Äpfeln in Bananen zulässt ohne auch nur zu wissen, dass beides Obst ist (will heißen: es wird nicht wirklich empfohlen ihn zu verwenden, wo es sich vermeiden lässt).
Aber was mich schon jetz fragwürdig macht ist, wie ich die eingelesene Zeichenkette(char *) dann in ein float wert, geschweigendenn in einen DWORD wert umwandeln soll.
Das ist schon gesehen.
ostream.write(reinterpret_cast<char *>(&(Config::Dateiformatversion)),sizeof(Config::Dateiformatversion)); 
ofstream::write erwartet einen char* und eine Schreiblänge. Dateiformatversion ist ein int. Und mit "reinterpret_cast<char *>(&(Config::Dateiformatversion))" wird ein char* erzeugt, der auf diesen int zeigt. Damit ist ofstream glücklich und klatscht die Daten in den Stream/File (und mit sizeof(Config::Dateiformatversion) gibt man an, wie weit das gehen soll).
Beim Einlesen ist es das gleiche:
ifstream::read ( char* s, streamsize n ); liest n Byte aus dem Stream und schreibt sie an die Stelle, auf die s zeigt.
Da ich weiß, dass im Stream als nächstes "DateiformatVersion" folgt will ich also sizeof(DateiformatVersion) einlesen. Schreiben will ich das eigentlich an DateiformatVersion*, was die read-Methode aber nicht schlucken würde. Also mache ich vorher ein char* draus.

Gruß

Filipp
Member: Bulleye
Bulleye Jun 11, 2009 at 16:23:30 (UTC)
Goto Top
Sry. die deklaration ist vllt. ein bisschen untergekommen.. deshalb hier nochmal:

ostream pFile;

Ok. Mal angenommen das konvertieren in meine Struktur würde ich diesem Code überlassen:

	// Convert pData to Vertex3d
	int size = FileStats.st_size / sizeof(Vertex3d);
	int pos = 0;
	Vertex3d *tBuffer = (Vertex3d*)malloc(sizeof(Vertex3d)*size);
	for (int i=0;i<size;i++) {
	
		pos = i*3;
		tBuffer[i].vPosition.x = pData[pos];
		tBuffer[i].vPosition.y = pData[pos+1];
		tBuffer[i].vPosition.z = pData[pos+2];
		tBuffer[i].color = D3DCOLOR_XRGB((int)pData[pos+3],(int)pData[pos+3],(int)pData[pos+3]);

	}

wärend Vertex3d die im Thread-Start stehende Struktur ist, aber ein (wegen der farbe) neues Element hat:

struct Vertex3d
{
FLOAT x,y,z;   // Positionen des Vertex
DWORD color;  // DWORD-EndFarbwert(berechnung über D3DCOLOR_XRGB())
INT cred, cgreen, cblue;  // RGB-Farbwerte
};

Der code ist jetzt für ein Dest-Array gebastelt, das z.B. so aussehen könnte:

.vPosition.x = 1.0; .vPosition.y = 1.0; .vPosition.z = 1.0;
.cred = 100; .cgreen = 100; .cblue = 100;

Die Datei müsste dementsprechend im Hex-editor so aussehen:

010101646464 ^= ddd

also wäre meine Theorie, jeweils ein Zeichen einzulesen, in Float-zu übersetzen und in die entsprechende Position im pData-Array zu packen.
aber ifstream::get liefert keinen binär eingelesenen Wert, sondern nichts zurück, weil der Text eben binär, und nicht im ASCII format ist.
Oder irre ich mich? face-smile

Hat jemand eine Lösung für das einlesen von binären Zeichen und die umwandlung in einen Float wert?
Member: filippg
filippg Jun 11, 2009 at 16:46:36 (UTC)
Goto Top
Hat jemand eine Lösung für das einlesen von binären
Zeichen und die umwandlung in einen Float wert?
Ich dachte, die hätte ich dir das gerade realtiv ausführlich dargelegt. Und im Internet findet man dazu auch noch einiges unter dem Begriff "c++ serialization".
Und wenn du das alles von mir nicht hören willst lass dir wenigstens sagen: ifstream::get ist für deinen Zweck ungeeignet, weil es nur ints zurückgeben kann. Das kannst du der Definition der Methode entnehmen und es steht in jeder C++-Referenz. Verwende stattdessen ifstream::read, das Daten blockweise binär einlesen kann.

Gruß

Filipp
Member: Bulleye
Bulleye Jun 11, 2009 at 18:27:46 (UTC)
Goto Top
Ok. Wenn ich Daten blockweise einlesen müsste, würde ein block 6 zeichen lang sein.

Wenn ich jetzt einen float-wert Block einlesen würde mit folgender Anweisung...

pFile.read(reinterpret_cast<char *>(&pData), sizeof(pData));

...Dann wäre ja ein Block gleich die ganze Datei, da sizeof(pData) in dem Moment gleich FileStats.st_size, also die größe der Datei ist.
Also müsste ich schreiben

pFile.read(reinterpret_cast<char *>(&pData), sizeof(float)*6);

Demnach müsste ich eine Schleife erstellen, die, die Sprungweite 6 hat und bis zu FileStats.st_size geht.

Sind meine Gedankengänge so richtig?

Lg