CStringW zu std:string



  • hustbaer schrieb:

    Und wozu die Frickelei mit Zeiger

    War ja nur ein Beispiel, wie man hier ganz grundsätzlich vorgehen kann, und nicht die Empfehlung, dass er den Code 1:1 in sein Projekt kopieren soll...

    hustbaer schrieb:

    und new ? Schonmal was von RVO/NRVO gehört? 😉

    Nein, noch nie :p

    hustbaer schrieb:

    Und das wctomb könnte man auch weglassen, wenn garantiert ist dass immer nur gültige Base64-Zeichen enthalten sind

    Wenn man sich tatsächlich darauf verlassen kann, dann ja.

    hustbaer schrieb:

    So wie du es machst werden z.B. sowieso keine echten "mb" Codepages unterstützt, da du nur ein "b" pro "w" erlaubst.

    In der Standard-Locale ist MB_CUR_MAX zwar gleich 1, aber darauf sollte man sich besser nicht verlassen und das Ausgabe-Array immer entsprechend des aktuellen MB_CUR_MAX allokieren, da es sonst zu einem Puffer-Überlauf kommen könnte.

    Dass wir später nur das erste Byte aus dem Ausgabe-Array übernehmen sollte kein Problem sein, da praktisch alle Multibyte-Kodierungen zu ASCII abwärts-kompatibel sind, d.h. alle ASCII-Zeichen werden mit einem einzigen Byte und mit dem selben Bit-Mutser wie im ASCII-Standard dargestellt (Beispiel UTF-8: Jeder ASCII-kodierte Text ist automatisch auch ein 100% gültiger UTF-8 Text; In Umgekehrter Richtung gilt das natürlich nur, wenn der UTF-8 Text keine Zeichen enthält, die in ASCII nicht darstellbar sind).



  • Danke für eure Antworten,

    ich habe es der Einfachheit halber auf CStringA umgebaut.

    Um es dann ohne kopieren auf std::String zu bekommen, habe ich folgendes programmiert:

    const int len = cstrSource.GetLength();
        std::string *outputBuffer = new std::string();
    
    	for(int i = 0; i < len; i++)
        {   
    		(*outputBuffer) += cstrSource.GetAt(i);
        }
    

    Ich habe jetzt aber noch nicht 100% verstanden was da eigentlich passiert? Ich hole einen Character aus dem CString und kopiere den in den Pointer? Oder wird da nur eine Referenz gesetzt? Wie sieht das intern aus?



  • lulu32_0 schrieb:

    Danke für eure Antworten,

    ich habe es der Einfachheit halber auf CStringA umgebaut.

    Um es dann ohne kopieren auf std::String zu bekommen, habe ich folgendes programmiert:

    const int len = cstrSource.GetLength();
    std::string *outputBuffer = new std::string();
    
    for(int i = 0; i < len; i++)
    {   
        (*outputBuffer) += cstrSource.GetAt(i);
    }
    

    Ich habe jetzt aber noch nicht 100% verstanden was da eigentlich passiert? Ich hole einen Character aus dem CString und kopiere den in den Pointer? Oder wird da nur eine Referenz gesetzt? Wie sieht das intern aus?

    Falls "cstrSource" ein** CStringW **ist:

    Du allokierst zunächst einen neuen leeren** std::string . Anschließend fügst Du dann den CStringW "cstrSource" Zeichen für Zeichen via += Operator an Deinen std::string **an.

    Die einzelnen Zeichen, die Du Dir von Deinem** CStringW per GetAt() abholst, werden dabei einfach von wchar_t auf char "abgeschnitten", da Du auf die wctomb() **Variante verzichtet hast.

    _______________

    Falls "cstrSource" hingegen ein** CStringA **ist, dann könntest Du es doch gleich so machen:

    std::string *outputBuffer = new std::string(cstrSource.GetBuffer());
    
    std::string outputBuffer(cstrSource.GetBuffer());
    


  • Irgendwie scheint es nur so zu funktionieren:

    CStringW hellocw ( _T("Hello World!") );
    CStringA helloca ( hellocw );
    string hellos ( helloca.GetString() );
    


  • EOP schrieb:

    Irgendwie scheint es nur so zu funktionieren:

    CStringW hellocw ( _T("Hello World!") );
    CStringA helloca ( hellocw );
    string hellos ( helloca.GetString() );
    

    Nö, geht auch so:

    CStringW hellocw (L"Hello World!");
    const int len = hellocw.GetLength();
    std::string hellos;
    hellos.reserve(len);
    for(int i = 0; i < len; i++)
    {
    	hellos += (char) hellocw.GetAt(i);
    }
    printf("RESULT: \"%s\"\n", hellos.c_str());
    

    Immer vorausgesetzt, in "hellocw" kommen tatsächlich nur ASCII-Zeichen vor - sonst bekommst da Blödsinn raus 😉



  • Viele Wege führen nach Rom.

    Ob das aber Sinn macht die ganze Konvertierung zu Fuß zu programmieren. Bin mir nicht so sicher.



  • EOP schrieb:

    Viele Wege führen nach Rom.

    Ob das aber Sinn macht die ganze Konvertierung zu Fuß zu programmieren. Bin mir nicht so sicher.

    Naja, den** CStringW zuerst in einen CStringA zu kopieren, nur um ihn dann ein weiteres mal in einen std::string **zu kopieren, erscheint mir aber irgendwie wenig sinnvoll 😕

    Außerdem kann der Konstruktor von** CStringA , der einen CStringW als Argument annimmt, intern letztendlich auch nichts anderes tun, als den Eingabe-String Zeichen für Zeichen von wchar_t nach char **zu konvertieren.

    Da kann man diese Konvertierung auch direkt von** CStringW nach std::string **implementieren und sich mindestens eine überflüssige Deep-Copy sparen...



  • Ende der 80er hab ich auch den TurboC code als .asm ausgeben lassen und den code dann händisch optimiert.

    Braucht man das heutzutage noch? Eher nein wenn man nicht gerade ne Marsmission oder ne Herzpumpe programmiert.

    Also wieso sollte ich das zum 100000sten Mal selber programmieren wenn es auch einfacher geht?

    EDIT:
    Wenn du gleich nach ASCII umwandelst und base64 decodierst sparst du eine Menge an Speicher.
    50% gegenüber UNICODE und ungefähr nochmal 4/5 nach dem base64 decodieren.

    EDIT #2:
    Quatsch, du sparst natürlich 1/5 und nicht 4/5.



  • EOP schrieb:

    Ende der 80er hab ich auch den TurboC code als .asm ausgeben lassen und den code dann händisch optimiert.

    Braucht man das heutzutage noch? Eher nein wenn man nicht gerade ne Marsmission oder ne Herzpumpe programmiert.

    Also wieso sollte ich das zum 100000sten Mal selber programmieren wenn es auch einfacher geht?

    Wieso sollte man Daten nicht unnötig 2x hin und her kopieren, wenn es sich auch relativ leicht vermeiden lässt? Die Frage beantwortet sich ja von selbst.

    Zuerst immer schnellere Rechner und immer größeren Arbeitsspeicher entwickeln, nur um diese dann mit ineffizientem Code und durch Speicherverschwendung auszulasten, kann ja wohl nicht der Sinn der Übung sein.

    Außerdem war die ursprüngliche Frage hier ja gerade, wie man einen** CStringW möglichst effizient in einen std::string **"umwandeln" kann, insbesondere im Hinblick auf den Speicherverbrauch.

    EOP schrieb:

    EDIT:
    Wenn du gleich nach ASCII umwandelst und base64 decodierst sparst du eine Menge an Speicher.
    50% gegenüber UNICODE und ungefähr nochmal 4/5 nach dem base64 decodieren.

    EDIT #2:
    Quatsch, du sparst natürlich 1/5 und nicht 4/5.

    Deswegen hatte ich dem OP ja schon am Anfang empfohlen möglichst von vorne herein einen ANSI-String anstelle eines Wide-Strings für seine Base64-kodierten Daten zu verwenden, oder, falls möglich, die Base64-kodierten Daten direkt in Binär-Daten zu dekodieren.

    Es scheint aber so zu sein, dass er die Base64-Daten nun mal als** CStringW herein bekommt und als std::string **in eine Funktion weiter reichen muss...



  • DeathCubeK schrieb:

    Dass wir später nur das erste Byte aus dem Ausgabe-Array übernehmen sollte kein Problem sein, da praktisch alle Multibyte-Kodierungen zu ASCII abwärts-kompatibel sind, d.h. alle ASCII-Zeichen werden mit einem einzigen Byte und mit dem selben Bit-Mutser wie im ASCII-Standard dargestellt (Beispiel UTF-8: Jeder ASCII-kodierte Text ist automatisch auch ein 100% gültiger UTF-8 Text; In Umgekehrter Richtung gilt das natürlich nur, wenn der UTF-8 Text keine Zeichen enthält, die in ASCII nicht darstellbar sind).

    Ja, ich weiss dass fast alle MB-Codepages mit den 128 ASCII Codes anfangen. Gerade deswegen kann man das wctomb auch ganz weglassen, wenn garantiert ist dass man nur ASCII reinbekommt 😉



  • Danke für eure Antworten,

    ich habe jetzt von CStringW auf CStringA umgebaut.

    Wird hier nicht kopiert? Ich möchte nicht kopieren da ich dann soviel speicher brauche.

    std::string outputBuffer(cstrSource.GetBuffer());
    


  • lulu32_0 schrieb:

    Danke für eure Antworten,

    ich habe jetzt von CStringW auf CStringA umgebaut.

    Wird hier nicht kopiert? Ich möchte nicht kopieren da ich dann soviel speicher brauche.

    std::string outputBuffer(cstrSource.GetBuffer());
    

    Doch, hier wird kopiert (von CString nach std::string).
    cstrSource.ReleaseBuffer() nicht vergessen, gelle...



  • lulu32_0 schrieb:

    Danke für eure Antworten,

    ich habe jetzt von CStringW auf CStringA umgebaut.

    Wird hier nicht kopiert? Ich möchte nicht kopieren da ich dann soviel speicher brauche.

    std::string outputBuffer(cstrSource.GetBuffer());
    

    Es gibt, soweit ich weiß, keinen** std::string Konstruktor, dem man einen existierenden Puffer zur Weiterverwendung unterschieben kann (bei QByteArray **ginge das, siehe hier).

    ** std::string **allokiert stets seinen eignen internen Puffer und im Konstruktor wird der Eingabe-String in diesen Puffer kopiert:

    from c-string (4)
    ** string (const char* s); **
    Copies the null-terminated character sequence (C-string) pointed by s.

    from sequence (5)
    ** string (const char* s, size_t n); **
    Copies the first n characters from the array of characters pointed by s.

    Ich denke daher, es lässt sich nicht verhindern, dass die Daten zumindest 1x kopiert werden, wenn es ein** std::string **werden soll/muss.

    _________

    Indem Du zuvor bereits durchgängig mit** CStringA arbeitest, sparst Du Dir aber immerhin eine unnötige Kopie und der Overhead für die wchar_t nach char **Konverteirung entfällt.



  • Vielen Dank, ich glaube ich verstehe es jetzt.

    DeathCubeK schrieb:

    EOP schrieb:

    Irgendwie scheint es nur so zu funktionieren:

    CStringW hellocw ( _T("Hello World!") );
    CStringA helloca ( hellocw );
    string hellos ( helloca.GetString() );
    

    Nö, geht auch so:

    CStringW hellocw (L"Hello World!");
    const int len = hellocw.GetLength();
    std::string hellos;
    hellos.reserve(len);
    for(int i = 0; i < len; i++)
    {
    	hellos += (char) hellocw.GetAt(i);
    }
    printf("RESULT: \"%s\"\n", hellos.c_str());
    

    Immer vorausgesetzt, in "hellocw" kommen tatsächlich nur ASCII-Zeichen vor - sonst bekommst da Blödsinn raus 😉

    Hierzu aber noch eine Frage. Was meinst du mit nur ASCII - Zeichen? Gibt es in Unicode andere zeichen?



  • lulu32_o schrieb:

    Vielen Dank, ich glaube ich verstehe es jetzt.

    DeathCubeK schrieb:

    EOP schrieb:

    Irgendwie scheint es nur so zu funktionieren:

    CStringW hellocw ( _T("Hello World!") );
    CStringA helloca ( hellocw );
    string hellos ( helloca.GetString() );
    

    Nö, geht auch so:

    CStringW hellocw (L"Hello World!");
    const int len = hellocw.GetLength();
    std::string hellos;
    hellos.reserve(len);
    for(int i = 0; i < len; i++)
    {
    	hellos += (char) hellocw.GetAt(i);
    }
    printf("RESULT: \"%s\"\n", hellos.c_str());
    

    Immer vorausgesetzt, in "hellocw" kommen tatsächlich nur ASCII-Zeichen vor - sonst bekommst da Blödsinn raus 😉

    Hierzu aber noch eine Frage. Was meinst du mit nur ASCII - Zeichen? Gibt es in Unicode andere zeichen?

    ASCII war einer der ersten binären Kodierungen für Schriftzeichen überhaupt (noch aus der Fernschreiber-Ära). ASCII umfasst nur 128-Zeichen und ist eigentlich ein 7-Bit Code, wobei aber praktisch immer 8-Bit (d.h. ein char in C/C++) pro Zeichen gespeichert wird:
    http://www.asciitable.com/index/asciifull.gif

    Offensichtlich ist ASCII in seinem Umfang stark begrenzt und vor allem für die englische Sprache ausgelegt, weshalb später diverse Codepages für verschiedene Sprachen eingeführt wurden, z.B. Latin-1 (ISO 8859-1) oder Kyrillisch (ISO 8859-5). Diese Codepages ordnen die 256 möglichen Werte eines Bytes bzw. char's je nach Sprache unterschiedlich zu. Dabei sind die "unteren" 128 Zeichen in aller Regel mit ASCII identisch, die "oberen" 128 Zeichen hingegen werden Sprach-spezifisch zugeordnet.

    Der große Nachteil an Codepages ist, dass man je nach verwendeter Codepage immer nur Zeichen einer Sprache darstellen kann. Du könntest also nie lateinische, kyrillische und japanische Zeichen in einem Text mischen! Außerdem ergibt ein Text nur dann Sinn, wenn er mit der "korrekten" Codepage interpretiert wird. Die ist aber im Allgemeinen System-spezifisch. Eine Datei, die auf einem japanischen System erstellt wurde, würde auf Deinem deutschen System also nur Kauderwelsch ergeben.

    Unicode ist der Versuch einen Standard zu schaffen, der alle Schriftzeichen enthält, die auf der Welt vorkommen. Dabei definiert Unicode aber zunächst einmal nur eine (sehr großen) Zeichenvorrat, aber keine konkrete Kodierung! Unicode Zeichen können z.B. als UTF-16 oder als UTF-8 kodiert werden. Unter Windows enthält ein** wchar_t -String normalerweise Unicode-Daten in der UTF-16 Kodierung. Unter Linux hingegen, wird üblicherweise UTF-8 benutzt, so dass die Unicode-Daten in einen "normalen" char **-String passen.



  • lulu32_0 schrieb:

    Wird hier nicht kopiert? Ich möchte nicht kopieren da ich dann soviel speicher brauche.

    std::string outputBuffer(cstrSource.GetBuffer());
    
    1. Das GetBuffer ist Unsinn, nimm GetString statt dessen. Dann brauchst du auch kein ReleaseBuffer.
    2. Wenn du willst dass gar nicht kopiert wird, dann schreib die Funktion um dass sie mit nem const char* arbeitet.
    3. Was du beschrieben hast was davor schon passiert klingt abenteuerlich - ich denke da wird auch einiges an sinnlosem Speicherverbrauch zu finden sein.
    4. Kauft einfach mehr RAM für die Kiste auf der das laufen soll. 16 GB kosten heutzutage echt nicht mehr die Welt, und dann können einem ein paar hundert MB Ramverbraucht normalerweise auch egal sein. (Es sei denn das Programm muss zig- oder hundertfach gleichzeitig laufen oder andere Extremfälle.) Rechne dir mal durch was selbst ein billiger Programmierer pro Arbeitstag kostet (inklusive Nebenkosten etc.), und dann vergleich das mit dem was 16 GB RAM kosten...


  • Das ist keine option. Das ganze wird auf mehreren tausend Rechner installiert die teilweise nur 1gb ram haben. Das Programm selber braucht im Idle 4mb Ram. Wenn dann die Prozedur startet geht es bis auf 120mb hoch. es kommen immer Datenblocke von ca. 10-200mb rein. Das ganze kommt als const char* an.

    Das muss dann gefiltert werden. Das ganze ist wie eine XML aufgebaut wobei jeder Tag nur einmal vorkommt und es keine Baumstruktur ist. Vor dem Datenstrom steht ein Schlusselwort "<DataStream>" Danach muss dann erst mal gesucht werden. Das Ende ist mit </DataStream>" markiert. Deswegen wird das ganze erstmal in einen CString konvertiert und dann unbenötigte teile "deleted". Anschließend bekomme ich diesen Stream zum Base64 decodieren. Hierzu benutze ich eine Funktion die einen std::String erwartet.

    Hmm wie kann ich denn im const char* nach "DataStream" suchen? Da müsste ich eine Funktion schreiben die nach "D" sucht und dann das nächste zeichen mit dem "Eingabestring" vergleicht. Das an das ende würde ich dann vermutlich einen Pointer zeigen lassen um danach die zeichen zählen bis ich das ende gefunden habe.. das wird alles sehr abenteuerlich^^

    BTW eine weitere Frage. Ich schreibe den decodierten Stream in eine Datei mit CFile. Mir ist jetzt aufgefallen, das ich auf C:\\ keine Datei anlegen kann (vermutlich rechte aufm Firmenrechner). Allerdings spuckt mir CFile keinen Fehler aus. Das tut so als ob nix wäre. Aber es wird keine Datei angelegt. Hab ich hier was vergessen?



  • 🙄 oje.



  • lulu32_0 schrieb:

    Das ist keine option. Das ganze wird auf mehreren tausend Rechner installiert die teilweise nur 1gb ram haben.

    OK, dann ist RAM Upgrade wohl wirklich keine (gute) Option.

    lulu32_0 schrieb:

    Hmm wie kann ich denn im const char* nach "DataStream" suchen? Da müsste ich eine Funktion schreiben die nach "D" sucht und dann das nächste zeichen mit dem "Eingabestring" vergleicht. Das an das ende würde ich dann vermutlich einen Pointer zeigen lassen um danach die zeichen zählen bis ich das ende gefunden habe.. das wird alles sehr abenteuerlich^^

    Naja es gibt die fertige Funktion strstr die man verwenden könnte.

    lulu32_0 schrieb:

    BTW eine weitere Frage. Ich schreibe den decodierten Stream in eine Datei mit CFile. Mir ist jetzt aufgefallen, das ich auf C:\\ keine Datei anlegen kann (vermutlich rechte aufm Firmenrechner).

    Im Rootverzeichnis eines Volumes haben normale Benutzer normalerweise nicht das Recht Dateien zu erzeugen.

    lulu32_0 schrieb:

    Allerdings spuckt mir CFile keinen Fehler aus. Das tut so als ob nix wäre. Aber es wird keine Datei angelegt. Hab ich hier was vergessen?

    Möglicherweise werden die Files von Windows "umgeleitet". Bei Root-Verzeichnissen wäre mir diesbezüglich zwar nix bekannt, aber du kannst ja mal unter %LocalAppData%\VirtualStore nachgucken, ob dein File vielleicht da zu finden ist.

    Die Lösung ist aber auf jeden Fall einen anderen Pfad zu verwenden. Mach einfach ein eigenes Verzeichnis irgendwo, da darfst du dann reinschreiben.



  • ps:

    lulu32_0 schrieb:

    Vor dem Datenstrom steht ein Schlusselwort "<DataStream>" Danach muss dann erst mal gesucht werden. Das Ende ist mit </DataStream>" markiert. Deswegen wird das ganze erstmal in einen CString konvertiert und dann unbenötigte teile "deleted".

    Du musst gar nix löschen.
    Es reicht z.B. wenn du das "<" von "</DataStream>" mit einer binären Null ("\0") überschreibst.
    Dann kannst du den Zeiger auf das Zeichen hinter dem ">" von "<DataStream>" an jede Funktion übergeben die einen C-String (= const char* ) erwartet, und es wird funktionieren.

    lulu32_0 schrieb:

    Anschließend bekomme ich diesen Stream zum Base64 decodieren. Hierzu benutze ich eine Funktion die einen std::String erwartet.

    Mach die Base64 Dekodierung selbst, direkt mit dem const char* . Ist ja nicht so schwer.
    Dann kannst du an die Funktion auch gleich die Länge des (kodierten) Base64 Strings übergeben, und kannst dir das Überschreiben des "<" mit "\0" sparen.

    So kannst du das alles machen ohne jemals irgendwo grössere Speichermengen anzufordern. Abgesehen von dem einen grossen Puffer in dem die ganze Nachricht inklusive der pseudo-XML Tags drinsteht. Den auch noch zu eliminieren wäre dann aber einigermassen viel Aufwand, und wird vermutlich auch nicht nötig sein.


Anmelden zum Antworten