Stream in char* kopieren



  • Hallo zusammen,

    für ein binäres Dateiformat habe ich eine Klasse, die Daten zwecks Speicherung in einen Stream schreibt.

    Diese Klasse soll jetzt in einem anderen Programm verwendet werden. Dieses andere Programm arbeitet intern allerdings mit char*-Buffern.

    Meine Frage: Ist es möglich, den Inhalt eines Streams (ofstream) in einen char* zu kopieren? Dies wäre der Speichervorgang. Beim Laden würde der umgekerhte Weg gebraucht werden, also Inhalt eines char*-Buffers in einen ifstream.

    Hat jemand eine Idee?

    Vielen Dank!

    Philipp



  • Simples Beispiel:

    std::ifstream of;
      of.open("C:\\aoi_outp.txt");
      char s[32768];
      of.get(s,32768);
    

    Du brauchst natürlich einen ifstream, ofstream ist zum Schreiben.



  • _matze schrieb:

    Du brauchst natürlich einen ifstream, ofstream ist zum Schreiben.

    Vielen Dank! Das war's.

    Grüße
    Philipp



  • Leider hat sich jetzt ein neues Problemchen ergeben. Dazu muss ich, so glaub ich, ein wenig weiter ausholen. Intern bin ich jetzt auf einen stringstream umgestiegen, um zeitgleich lesen und schreiben zu können. Die Daten, die ich speichern möchte, sind größtenteils Doubles:

    sstream.write(reinterpret_cast<char *>(&y), sizeof(y));
    

    Die Konvertierung zwischen stream und char* schaut wie folgt aus:

    //stream in char*
      int size = sstream.tellp();
      char* s = new char[size];
      sstream.seekg(0);
      sstream.get(&s[0],size);
    

    Je nach Daten kommt es manchmal vor, dass der Buffer s nicht ganz gefüllt wird, also irgendwann der Inhalt des Streams einfach abgeschnitten wird.

    Woran könnte das liegen? In der Doku steht, dass get bei bestimmten Zeichen den zweiten Parameter ignoriert und aufhört zu lesen. Kann man das umgehen?

    Vielen Dank schon mal!

    Grüße
    Philipp



  • Umgehen tut man das, indem man vor jedem String, den man speichert die Länge mit angibt. Dann weiß man beim Auslesen immer wieviel Byte zu lesen sind.
    Ein double liest und schreibt man aber als double. Hier ist natürlich keine Längenangabe nötig, es sei denn man will Daten zwischen Sytemen austauschen, deren Double unterschiedliche Längen haben. In so einem Fall ist binäres lesen und Schreiben aber eh eine schlechte Idee.



  • du weißt schon, dass man mit stream.str().c_str() nen const char* bekommt.



  • Hallo Braunstein,

    Braunstein schrieb:

    Umgehen tut man das, indem man vor jedem String, den man speichert die Länge mit angibt. Dann weiß man beim Auslesen immer wieviel Byte zu lesen sind.

    Bei Strings klappt das auch. Ich speichere die Länge und dann die Zeichen nud beim Laden lese ich die Länge, setze meinen String auf diese Länge und lese die Zeichen.

    Ein double liest und schreibt man aber als double.

    Genau das wollte ich eigentlich tun. Ich wollte einen Double binär speichern. Die einzige Möglichkeit, die ich gefunden habe, war, den Double in einen char* zu casten:

    sstream.write(reinterpret_cast<char *>(&y), sizeof(y));
    

    Einlesen geschieht dann so:

    stream.read(reinterpret_cast<char *>(&y), sizeof(y));
    

    Das funktioniert auch alles prima. Im Programm selbst arbeite ich ja mit Streams. Ich kann die Streams laden und speichern, die Werte sind die gleichen.

    Die neue Situation ist, dass die Klasse in einem anderen Programm eingesetzt wird und dass ich hier die Daten nur als char* bekomme (beim laden) und die Daten auch nur in char* schreiben kann. Und dieses hin- und hergeschiebe geht halt in der Hinsicht schief, falls ganz bestimmte Doubles auftreten (konkret: 0.751293) kopiert er den Stream-Inhalt nicht richtig in den char* (siehe obigen Quelltext).

    Meine Vermutung ist: In der Binärdarstellung von 0.751293 tritt ein Zeichen auf (vielleicht #0?), wodurch sich der get Befehl dazu veranlasst fühlt, die Übertragung abzubrechen.

    @mycity: Danke für den Hinweis. Ich kannte es noch nicht. Aber es hat leider nicht funktioniert. Auch hier wird der Stream abgeschnitten, allerdings an einer anderen Stelle.

    Alles sehr komisch...

    Schönen Abend!

    Ciao
    Philipp



  • Ein stringstream ist nicht dazu da um ein double in ein file zu schreiben. was soll das werden?



  • Ja, das macht vom Namen her gesehen irgendwie Sinn ;).

    Ok, ich versuche mein Problem nochmal anders zu schildern.

    Ich habe eine Struktur, die ich speichern möchte. Zu Speichern sind u.a. Strings, Doubles, long longs usw. Die Klasse wird später in unterschiedlichen Programmen verwendet. Ein Programm erwartet einen Stream und ein anderes Programm erwartet, dass ich in einen char* schreibe und lese.

    Welchen Stream-Typ könnte ich sinnvollerweise verwenden?

    Ich komme eigentlich von der Programmiersprache Delphi. Dort habe ich eine Klasse TMemoryStream. Mithilfe dieser Klasse kann ich beliebige Typen einfach binär in einen Stream lesen und schreiben. Gibt es eine ähnliche Klasse in C++?

    Danke!
    Philipp



  • PhilippF schrieb:

    ...Ich wollte einen Double binär speichern....

    Warum ?
    Ist oftmals keine gute Idee.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    PhilippF schrieb:

    ...Ich wollte einen Double binär speichern....

    Warum ?
    Ist oftmals keine gute Idee.

    Welche Alternative würde es geben? Eine andere Alternative, die mir einfällt, wäre ihn als String zu speichern. Nur das macht das Einlesen und Auslesen durch die Konvertierungen wirklich langsam.



  • PhilippF schrieb:

    ...Eine andere Alternative, die mir einfällt, wäre ihn als String zu speichern. Nur das macht das Einlesen und Auslesen durch die Konvertierungen wirklich langsam.

    Hast Du das mal ausprobiert ?
    Also das soll jetzt nicht unfreundlich klingen, aber in mindestens 90% der Fälle hier sind solche "Performanceverlustängste" vollkommen unbegründet ... und in 90% der verbleibenden Fälle gibt's bessere Lösungen für (nachgewiesene) Performanceprobleme.

    Du weißt schon, dass Binärspeichern Compiler-/Plattform-/Einstellungs-/...abhängige Files erzeugt, oder ? Wenn Du also später mal diese Files wieder einlesen möchtest, hast Du gute Chancen, die Daten gar nicht mehr einlesen zu können...

    Gruß,

    Simon2.



  • Simon2 schrieb:

    PhilippF schrieb:

    ...Eine andere Alternative, die mir einfällt, wäre ihn als String zu speichern. Nur das macht das Einlesen und Auslesen durch die Konvertierungen wirklich langsam.

    Hast Du das mal ausprobiert ?

    Also das soll jetzt nicht unfreundlich klingen, aber in mindestens 90% der Fälle hier sind solche "Performanceverlustängste" vollkommen unbegründet ... und in 90% der verbleibenden Fälle gibt's bessere Lösungen für (nachgewiesene) Performanceprobleme.

    Keine Sorge, das klingt nicht unfreundlich ;). Ich bin ja auch der, der um Hilfe bittet und da ist mir jede Hilfestellung recht. Aber zu deiner Frage: Ja, die bisherige Lösung sieht so aus, dass die Doubles alle untereinander in eine Textdateigeschrieben werden. Die Analyse mit dem Profiler hat ergeben, dass die Konvertierung String<->Double die meiste Zeit in Anspruch nimmt. Es sind übrigens sehr viele Messdaten, die über Jahre erfasst werden und dann ausgewertet werden. Da macht sich der Zeitunterschied zur Binärspeicherung bemerkbar, wie wir bereits getestet haben.

    Du weißt schon, dass Binärspeichern Compiler-/Plattform-/Einstellungs-/...abhängige Files erzeugt, oder ? Wenn Du also später mal diese Files wieder einlesen möchtest, hast Du gute Chancen, die Daten gar nicht mehr einlesen zu können...

    Dieses Problem haben wir diskutiert. So häufig wird sich unser System nicht ändern. Falls es dann zu einer Systemänderung kommt, sollen die Daten dann alle in einem anderen Format, beispielsweise als Plaintext gespeichert werden. Liest man diesen Plaintext im neuen System wieder ein, kann er dann wieder - mit der anderen Darstellung - binär gespeichert werden. Dies wäre dann ein einmaliger Vorgang im Gegensatz zum normalen Laden und Speichern, der durchaus häufiger auftauchen könnte.

    Grüße
    Philipp



  • OK,

    das klingt schon besser (Du fällst damit anscheinend in das verbleibende Prozent 😉 ).

    Das bedeutet also, dass Ihr ein "internes" und ein "portables" Format habt ... und es geht Dir hier um die Implementation des internen. Finde ich eine gute Lösung (solange ihr regelmäßig Eure Daten im portablen Format sichert).

    PhilippF schrieb:

    ...Dort habe ich eine Klasse TMemoryStream. Mithilfe dieser Klasse kann ich beliebige Typen einfach binär in einen Stream lesen und schreiben. Gibt es eine ähnliche Klasse in C++? ...

    Nicht, dass ich wüsste. Das Problem ist eben, dass in C++ (leider ? zum Glück ?) kein "Speicherlayout" definiert wurde....

    Mich wundert Dein unten geschilderte "Abbruch beim Lesen". Öffnest du den Stream mit binär-Flag ?
    Ich würde mal versuchen, byteweise in ein char[] einzulesen und dann den reinterpret_cast<double*>(&c[0]) darauf zu machen...

    Gruß,

    Simon2.



  • Habt ihr mal daran gedacht eine DB zu verwenden?
    z.B.
    http://www.firebirdsql.org/ (da gibt es eine embedded Version)
    http://www.sqlite.org/

    Das kann einiges vereinfachen und auch die Performance verbessern.



  • Ich weiss nicht, obs schon gepostet wurde.
    Binär könntest du (müsstest du) das etwa so machen

    double myDouble = 0.0;
    ...
    
    ofstream file("...",ios::binary);
    file.write(reinterpret_cast<char*>(&myDouble),sizeof(myDouble));
    
    // und das lesen ist ähnlich
    double myDouble
    ifstream file("...",ios::binary);
    file.read(reinterpret_cast<char*>(&myDouble),sizeof(myDouble));
    


  • Hallo,

    erst nochmal vielen Dank! Es ist echt toll, wieviel Mühe sich hier gegeben wird.

    Ich habe unser Problem jetzt mal auf ein Minimalbeispiel heruntergebrochen. Hier wird das Schreiben in einen Stream, das Kopieren in den Buffer, das Kopieren vom Buffer in den Stream und das Lesen aus dem Stream durchgeführt. Die ersten beiden Schritte sind nötig für einen Speichervorgang, die zweiten beiden für einen Ladevorgang.

    Kurz nochmal zur Erläuterung: Der Klimmzug stream<->Buffer ist nötig, weil in der Klasse, die das interne Format verwaltet mit Streams gearbeitet wird. Diese Klasse wird in unterschiedlichen Programmen verwendet. Die unterschiedlichen Programme stellen jeweils Schnittstellen zum Laden und Speichern zur Verfügung. Problematisch ist hier, dass die Schnittstelle im einen Programm über char* kommuniziert und das andere Programm über Streams.

    Zurück zur Beispielroutine: Die untenstehende Version macht genau das gewünschte. Ersetzt man jetzt die 47.11 durch die andere Zahl, die im Kommentar angegeben ist, kommt Müll heraus.

    Ich hoffe, das verdeutlicht mein Problem besser. Bin dankbar für jegliche Tipps

    Grüße
    Philipp

    P.S. Habe gesehen, dass mittlerweile noch mehr Beiträge eingegangen sind. Das geht ja echt schnell ;).

    @ihoernchen: Danke, aber das war mir bereits bekannt. Genau so speichere ich in den Stream. Probleme gibt es beim Verschieben ins char*. Eine DB scheidet aus Gründen, die nicht in meiner Hand liegen, leider aus.

    P.P.S: Den Tipp mit dem byteweisen einlesen, werde ich mal testen. Glaubst du, dass dies sehr auf die Performance gehen wird?

    void minimalTest()
    {
    	double y;
    	long long x;
    	string caption;
    
    	stringstream stream;
    	int ssize;
    
    	//Diese Werte sollen gespeichert werden
    	caption = "Testbeispiel";
    	y = 47.11; //0.751293;
    	x = 100;
    
    	//Schreiben in den Stream
    	ssize = (int)caption.length();
    	stream.write(reinterpret_cast<char *>(&ssize), sizeof(ssize));
    	stream.write(reinterpret_cast<char *>(&caption[0]), ssize);
    	stream.write(reinterpret_cast<char *>(&y), sizeof(y));
    	stream.write(reinterpret_cast<char *>(&x), sizeof(x));
    
    	//Schreiben in den Buffer
    	int size = stream.tellp();
    	char* buffer = new char[size];
    	stream.seekg(0);
    	stream.get(&buffer[0],size);
    
    	//Zurückinitialisieren
    	caption = "";
    	y = 0.0;
    	x = 0;
    
    	//Buffer -> Stream
    	stringstream stream2;
    	stream2.write(buffer, size);
    	stream2.seekg(0, ios::beg);
    
    	//Lesen aus dem Stream in die Variablen
    	stream2.read(reinterpret_cast<char *>(&ssize), sizeof(ssize));
    	caption.resize(ssize);
    	stream2.read(reinterpret_cast<char *>(&caption[0]), ssize);
    	stream2.read(reinterpret_cast<char *>(&y), sizeof(y));
    	stream2.read(reinterpret_cast<char *>(&x), sizeof(x));
    
    	//Ausgabe
    	cout << caption << endl;
    	cout << y << endl;
    	cout << x << endl;
    }
    


  • Ändere mal bitte das Schreiben in den Puffer dahin ab.

    size_t size = stream.tellp();
    char* buffer = new char[size];
    stream.clear();
    stream.seekp(0, ios::beg);
    stream.readsome(buffer,size);
    

    So ähnlich mach ich das normalerweise.



  • PhilippF schrieb:

    //Schreiben in den Buffer
    	int size = stream.tellp();
    	char* buffer = new char[size];
    	stream.seekg(0);
    	stream.get(&buffer[0],size);
    
    }
    

    Ersetzt das durch

    //Schreiben in den Buffer
        int size = stream.tellp();
        char* buffer = new char[size];
        // falsch, siehe unten
        //stream.seekg(0);
        //stream.get(&buffer[0],size);
    
        memcpy(buffer,stream.str().c_str(),size);
    

    weil http://www.cplusplus.com/reference/iostream/istream/get.html:
    istream& get (char s, streamsize n );*
    Extracts characters from the stream and stores them as a c-string into the array beginning at s. Characters are extracted until either (n - 1) characters have been extracted or the delimiting character '\n' is found. The extraction also stops if the end of file is reached in the input sequence or if an error occurs during the input operation.
    If the delimiting character is found, it is not extracted from the input sequence and remains as the next character to be extracted. Use getline if you want this character to be extracted (and discarded).
    The ending null character that signals the end of a c-string is automatically appended at the end of the content stored in s.



  • Vielen, vielen Dank! Mit memcpy funktioniert es. Die Funktion scheint da rigoroser zu sein. Jetzt wird auch die böse 0.751293 eingelesen :).



  • Deswegen hatt ich readsome vorgeschlagen, das hat die Probleme von get auch nicht. 🙂


Anmelden zum Antworten