BinaryParser



  • Hi,

    ich habe hier mal einen "BinaryParser" gemacht (der Ausdruck ist etwas unglücklich gewählt, da es nicht wirklich ein Parser ist, der sich an einer Grammatik orientiert, sollte ich also mal in BinaryREader oder sowas umbenennen... bitte vergebt mir 😉 ).

    Ich habe ein kleines Testprogramm unten gepostet, dass den BinaryParser dazu benutzt, um den Header einer Windows-BMP einzulesen.
    Das Header-einlesen gew. Dateitypen stelle ich mir dann eigentlich auch als Anwendungsgebiet fuer den "Parser" vor.
    Aber schlagt mich nicht, wegen des Testproggies, es soll nur mal eine Anwendung demonstrieren - auf dessen Sinn erhebe ich dabei keinen Anspruch....

    Wollte mal Eure Meinung/Kritik zum BinaryParser hören.

    An die mit denen ich über die Konvertierungen gesprochen habe: ich habe mich erstmal für meine Version entschieden, nehme vielleicht später eine Eurer Versionen.

    #include <iostream>
    #include <fstream>
    using namespace std;
    
    class myBinaryParser{
     private:
     ifstream in;
     char* data;
     unsigned long pos;  //"Zeiger" auf aktulle Position in data-Array
    
     public:
     myBinaryParser(char* filepath, unsigned long bytesToRead){
            pos = 0;
            in.open(filepath, ios::binary);
    
            if(in){
                    data = new char[bytesToRead];
                    in.read(data, bytesToRead);
            }
            //Hm...also der Fehlerabfang sollte aus dem Konstruktor raus, weil ja sonst
            //trotzdem weitergearbeitet wird...
            else{
                    cout << "Fehler beim oeffnen der Datei." << endl;
                    cin.get();
            }
     }
     //...andere Konstruktoren...
     ~myBinaryParser(){
            in.close();
            delete[] data;
     }
    
     //ein paar nuetzliche Methoden, die entspr. Daten auslesen und die
     //Position dann auf den Anfang des naechsten zu parsenden "Ausschnittes" setzt:
    
     char getChar(void){
            char retVal = data[pos];
            pos++;
            return retVal;
     }
    
     //liefert 0-terminiertes char-Array zurueck
     char* getChar(int num){
            char* retVal = new char[num +1];
            for(int i=0; i<num; ++i){
                    retVal[i] = data[pos +i];
            }
            retVal[num] = '\0';
            pos +=num;
            return retVal;
     }
    
     WORD getWORD(void){
            WORD retVal = data[pos] + data[pos+1]*0x100;
            pos+=2;
            return retVal;
     }
    
     DWORD getDWORD(void){
            DWORD retVal = data[pos] + data[pos+1]*0x100 + data[pos +2]*0x10000 + data[pos +3]*0x100000000;
            pos+=4;
            return retVal;
     }
    
     unsigned long getPos(void){
            return pos;
     }
     //...weitere Methoden...
    };
    
    //simples Testprogramm fuer myBinaryParser: auslesen des Headers einer Windows BMP-Datei
    int main()
    {
            char *path = "d:\\Test\\Fahne.bmp";
    
            //Die Beschreibung habe ich glaube ich irgendwo bei wotsit.org gelesen
            int desCnt = 0;
            char* description[] = { "Datei-Identifikation (BM)",
                                    "Dateilaenge in Byte",
                                    "reserviert (0)",
                                    "Zeiger auf Pixeldaten (relativ zum Dateianfang) ",
                                    "Headergroesse (40)",
                                    "Bildbreite (in Pixeln)",
                                    "Bildhoehe  (in Pixeln) ",
                                    "Anzahl Ebenen",
                                    "Bits pro Pixel",
                                    "Kompression (0 = unkomprimiert)",
                                    "Groesse der Pixeldaten",
                                    "X-Aufloesung (in Pixel pro Meter)",
                                    "Y-Aufloesung (in Pixel pro Meter)",
                                    "Anzahl genutzter Farben (nur bei Palette)",
                                    "Anzahl wichtiger Farben (nur bei Palette)"   };
    
            int sizeofHeader = 12*sizeof(DWORD) + 2*sizeof(WORD) + 2*sizeof(char);
            myBinaryParser p(path, sizeofHeader);
    
            cout <<"Header der Datei " <<path <<endl <<endl;
            cout <<"Offset  dez Wert  Beschreibung" <<endl <<endl;
    
            cout.width(4);      //scheiss Formatierungen immer,
            cout.fill('0');     //nur damit das Offset huebsch angezeigt wird :(
            cout <<hex << p.getPos() <<":   ";
    
            cout <<p.getChar(2) <<"\t  " <<description[desCnt++] <<endl;
    
            //6 mal DWORD einlesen
            for(int i=0; i<6; ++i){
                    cout.width(4);
                    cout.fill('0');
                    cout.setf(ios_base::uppercase);
                    cout <<hex <<p.getPos() <<":   ";
    
                    cout <<dec <<p.getDWORD() <<"\t  " <<description[desCnt++] <<endl;
            }
    
            //2 mal WORD einlesen
            for(int i=0; i<2; ++i){
                    cout.width(4);
                    cout.fill('0');
                    cout.setf(ios_base::uppercase);
                    cout <<hex <<p.getPos() <<":   ";
    
                    cout <<dec <<p.getWORD() <<"\t  " <<description[desCnt++] <<endl;
            }
    
            //6 mal DWORD einlesen
            for(int i=0; i<6; ++i){
                    cout.width(4);
                    cout.fill('0');
                    cout.setf(ios_base::uppercase);
                    cout <<hex <<p.getPos() <<":   ";
    
                    cout <<dec <<p.getDWORD() <<"\t  " <<description[desCnt++] <<endl;
            }
    
            cin.get();
            return 0;
    }
    


  • Ah...mist:
    Die Konvertirungen in myBinaryParser hauen nicht immer hin bei getWORD() und getDWORD()...
    ..habe sie ersetzt durch den Vorschlag von PAD mit den Unions:

    //unions zum konvertieren
    union ConvertDWORD{
            unsigned long ulong;
            char c[4];
    } conDWORD;
    
    union ConvertWORD{
            unsigned short ushort;
            char c[2];
    } conWORD;
    
    WORD getWORD(void){
            for(int i=0; i<2; ++i)
                    conWORD.c[i] = data[pos +i];
            WORD retVal = conWORD.ushort;
            pos+=2;
            return retVal;
     }
    
     DWORD getDWORD(void){
            for(int i=0; i<4; ++i)
                    conDWORD.c[i] = data[pos +i];
            DWORD retVal = conDWORD.ulong;
            pos+=4;
            return retVal;
     }
    

    Der Vorschlag von PeterTheMaster passt auch:

    DWORD getDWORD(void){
            char c[4];
            for(int i=0; i<4; ++i)
                    c[i] = data[pos +i];
            DWORD retVal = *reinterpret_cast<DWORD*>(c);
            pos+=4;
            return retVal;
     }
    

    Aber ich glaube, dass die Lösung mit den Unions wohl geringfügig schneller sein dürfte, oder ?
    (abgesehen davon, dass Geschwindigkeit in diesem Prog keine allzu grosse Rolle spielt 😉 )



  • Ich denke mal, dass die Sache mit der union +/- 2 Takte genau so schnell ist wie das casten. Instinktiv würde ich sagen, dass der cast schneller ist.



  • Geschwindigkeit von cast und union.
    Ich sehs genau andersrum die Union belegt mit 2 Variablennamen dieselbe Speicherstelle somit sind nur
    Memoryaccess nötig. Beim cast gehe ich imho davon aus, das da etwas mehr intern gedacht werden muß. Aber vielleicht arbeitet der Cast ja intern mit den Union?



  • Der Vorschlag von PeterTheMaster passt auch:

    Aber warum wird zusätzlich zur Kopie, die bei der Rückgabe angelegt wird, auch noch in ein lokales char-Array (mit for-Schleife) kopiert?



  • @PAD: Ja, man müsste mal im Disassembly nachgucken was der Cast bzw. die Union machen bevor man eine Aussage trifft. War aber nur so ne Vermutung von mir, da der Cast ja zur Compilezeit geschieht.



  • @MaSTaH

    💡 Denkfehler zur Compiletime wir die entsprechende Castingroutine in den Code eingefügt.
    ➡ Das casten selber findet zur runtime statt 😋



  • fit schrieb:

    #include <iostream>
    #include <fstream>
    using namespace std;
    
    class myBinaryParser{
     //liefert 0-terminiertes char-Array zurueck
     char* getChar(int num){
            char* retVal = new char[num +1];
            for(int i=0; i<num; ++i){
                    retVal[i] = data[pos +i];
            }
            retVal[num] = '\0';
            pos +=num;
            return retVal;
     }
    
    //simples Testprogramm fuer myBinaryParser: auslesen des Headers einer Windows BMP-Datei
    int main()
    {       /*speicher leck..... */
            cout <<p.getChar(2) <<"\t  " <<description[desCnt++] <<endl;
    
    }
    

    Gruß Mathik



  • @PAD: Hattest Recht. Die union braucht laut meinem Disassembly (ohne Optimierungen) weniger Befehle. Ich weiß nicht wie es aussieht wenn man ein memcpy anstatt der blöden Schleife benutzt. Da dürfte der cast aber IMHO schneller sein.



  • Hi, vielen Dank schonmal für Eure Antworten.

    @mathik:
    Was meinst Du mit Speicherleck ?
    Meinst Du, dass zu dem mit new angelegten char-Array kein entsprechendes delete existiert ?
    Jo stimmt, aber wie soll ich das realisieren, damit das Prog noch einigermassen hübsch im Handling ist ?
    Wenn ich jetzt z.b. eine Methode free() für die Klasse baue oder direkt nach dem benutzen immer lösche, wird leidet die Schnittstelle.

    Aber ihr habt recht, ich arbeite viel zu viel mit Kopien und zu wenig mit Referenzen.

    Jetzt ist aber das Problem, dass ich so nicht weiss, wie ich z.b. die Methode getChar(int) einfach nur eine Referenz auf die angeg. chars zurückgeben lasse, weil ich ja nicht das ganze data zurückgebe, sondern nur einen Teil davon.

    Wie mache ich das ?
    Ich kenn mich da nicht so aus, weil ich vorher von Java verwöhnt wurde 🙄 .



  • @MaSTaH

    Beim Dword könnte das interessant sein, da wir eine 32 Bit Maschine haben

    beim Word eher nicht

    🙄 Das errinert mich an meine alten Assemblerzeiten mit Cyclenzählen und Befehle sparen, lang lang ist´s her, und mit den neuen CPU´s auch nicht mehr so nachvollziehbar. 🙄



  • fit schrieb:

    Hi, vielen Dank schonmal für Eure Antworten.
    Jo stimmt, aber wie soll ich das realisieren, damit das Prog noch einigermassen hübsch im Handling ist ?
    Wenn ich jetzt z.b. eine Methode free() für die Klasse baue oder direkt nach dem benutzen immer lösche, wird leidet die Schnittstelle.

    Aber ihr habt recht, ich arbeite viel zu viel mit Kopien und zu wenig mit Referenzen.

    Jetzt ist aber das Problem, dass ich so nicht weiss, wie ich z.b. die Methode getChar(int) einfach nur eine Referenz auf die angeg. chars zurückgeben lasse, weil ich ja nicht das ganze data zurückgebe, sondern nur einen Teil davon.

    Du solltest aber darauf achten, dass du keine Refernz auf eine lokale Variable
    zurück gibst, und dass jede Referenz auf Member const ist. Wie wärs denn
    mit nem std::string statt dem char-array?



  • fit schrieb:

    Hi, vielen Dank schonmal für Eure Antworten.

    @mathik:
    Was meinst Du mit Speicherleck ?
    Meinst Du, dass zu dem mit new angelegten char-Array kein entsprechendes delete existiert ?
    Jo stimmt, aber wie soll ich das realisieren, damit das Prog noch einigermassen hübsch im Handling ist ?
    Wenn ich jetzt z.b. eine Methode free() für die Klasse baue oder direkt nach dem benutzen immer lösche, wird leidet die Schnittstelle.

    Aber ihr habt recht, ich arbeite viel zu viel mit Kopien und zu wenig mit Referenzen.

    Jetzt ist aber das Problem, dass ich so nicht weiss, wie ich z.b. die Methode getChar(int) einfach nur eine Referenz auf die angeg. chars zurückgeben lasse, weil ich ja nicht das ganze data zurückgebe, sondern nur einen Teil davon.

    Wie mache ich das ?
    Ich kenn mich da nicht so aus, weil ich vorher von Java verwöhnt wurde 🙄 .

    //entweder (ist am einfachsten)
    
     void getChar(std::string& retVal, int num);
    
     //oder: (ist nicht sonderlich performant)
     std::string getChar(int num); 
    
      //oder auto_ptr verwenden... ungefähr so (eleganteste lösung)
      auto_ptr<string> getChar(int num);
    

    Gruß mathik



  • @mathik: Thx erstmal.

    //entweder (ist am einfachsten) 
     void getChar(std::string& retVal, int num);
    

    Also das will ich nicht nehmen, weil das Konzept so gedacht war, das die Ergebnisse immer als return-Wert zurück kommen sollen.

    //oder: (ist nicht sonderlich performant) 
     std::string getChar(int num);
    

    das wuerde passen, die Implementierung ist aber sicher nicht der Hit (habe vorher fast nie mit std::string gearbeitet):

    //...
    string getChar(int num){
            string retVal;
            retVal.resize(num);
            for(int i=0; i<num; ++i){
                    retVal[i] = data[pos +i];
            }
            pos +=num;
            return retVal;
     }
    

    auto_ptr kenne ich nicht...
    Das data der Klasse will ich aber als char-Array lassen. Ich kann mir nicht vorstellen, dass es sinnvoll ist das durch string zu ersetzen.

    Ich habe noch eine andere Idee:
    Ich lasse die getChar() einfach so wie sie ist ( mit new char[num]) und erzeuge in der Klasse eine Tabelle (Array) von Zeigern, in die alle Zeiger reinkommen, die beim Aufruf von getChar() erzeugt wurden.
    Und im Destruktor mache ich dann eine Schleife, die über die Tabelle drüber läuft und alle Inhalte, auf die die Zeiger zeigen löscht.

    Dann müsste eigentlich auch alles wieder fein sauber nach der Benutzung sein..oder ?



  • fit schrieb:

    @mathik: Thx erstmal.

    //entweder (ist am einfachsten) 
     void getChar(std::string& retVal, int num);
    

    Also das will ich nicht nehmen, weil das Konzept so gedacht war, das die Ergebnisse immer als return-Wert zurück kommen sollen.

    //oder: (ist nicht sonderlich performant) 
     std::string getChar(int num);
    

    das wuerde passen, die Implementierung ist aber sicher nicht der Hit (habe vorher fast nie mit std::string gearbeitet):

    //...
    string getChar(int num){
            string retVal;
            retVal.resize(num);
            for(int i=0; i<num; ++i){
                    retVal[i] = data[pos +i];
            }
            pos +=num;
            return retVal;
     }
    

    auto_ptr kenne ich nicht...
    Das data der Klasse will ich aber als char-Array lassen. Ich kann mir nicht vorstellen, dass es sinnvoll ist das durch string zu ersetzen.

    Ich habe noch eine andere Idee:
    Ich lasse die getChar() einfach so wie sie ist ( mit new char[num]) und erzeuge in der Klasse eine Tabelle (Array) von Zeigern, in die alle Zeiger reinkommen, die beim Aufruf von getChar() erzeugt wurden.
    Und im Destruktor mache ich dann eine Schleife, die über die Tabelle drüber läuft und alle Inhalte, auf die die Zeiger zeigen löscht.

    Dann müsste eigentlich auch alles wieder fein sauber nach der Benutzung sein..oder ?

    @fit
    da ist eine ganz schlechte methode ;), nutz stattdessen den auto_ptr. hier ein linkt dazu: http://www.gotw.ca/publications/using_auto_ptr_effectively.htm

    ansonsten hat die string-klasse eine methode string::append(const char *s, size_type n);

    damit würdest du dir die schleife sparen...

    Gruß mathik



  • fit schrieb:

    @mathik: Thx erstmal.

    //entweder (ist am einfachsten) 
     void getChar(std::string& retVal, int num);
    

    Also das will ich nicht nehmen, weil das Konzept so gedacht war, das die Ergebnisse immer als return-Wert zurück kommen sollen.

    //oder: (ist nicht sonderlich performant) 
     std::string getChar(int num);
    

    das wuerde passen, die Implementierung ist aber sicher nicht der Hit (habe vorher fast nie mit std::string gearbeitet):

    //...
    string getChar(int num){
            string retVal;
            retVal.resize(num);
            for(int i=0; i<num; ++i){
                    retVal[i] = data[pos +i];
            }
            pos +=num;
            return retVal;
     }
    

    auto_ptr kenne ich nicht...
    Das data der Klasse will ich aber als char-Array lassen. Ich kann mir nicht vorstellen, dass es sinnvoll ist das durch string zu ersetzen.

    Ich habe noch eine andere Idee:
    Ich lasse die getChar() einfach so wie sie ist ( mit new char[num]) und erzeuge in der Klasse eine Tabelle (Array) von Zeigern, in die alle Zeiger reinkommen, die beim Aufruf von getChar() erzeugt wurden.
    Und im Destruktor mache ich dann eine Schleife, die über die Tabelle drüber läuft und alle Inhalte, auf die die Zeiger zeigen löscht.

    Dann müsste eigentlich auch alles wieder fein sauber nach der Benutzung sein..oder ?

    @fit
    da ist eine ganz schlechte methode ;), nutz stattdessen den auto_ptr. hier ein linkt dazu: http://www.gotw.ca/publications/using_auto_ptr_effectively.htm

    ansonsten hat die string-klasse eine methode string::append(const char *s, size_type n);

    damit würdest du dir die schleife sparen...

    Gruß mathik



  • fit schrieb:

    //oder: (ist nicht sonderlich performant) 
     std::string getChar(int num);
    

    Doch, wenn der Compiler NRVO durchführt, schon.



  • Ist das eine übliche Optimierung, oder machen das nur wenige Compiler?



  • Danke für Eure antworten.
    Habe das mit dem auto_ptr jetzt mal so probiert:

    auto_ptr<string> getString(int num){
            auto_ptr<string> retVal = new string;
            *retVal.resize(num);
            for(int i=0; i<num; ++i)
                    *retVal[i] = data[pos +i];
            pos +=num;
            return retVal;
     }
    

    Ergebnis:

    E2034 Konvertierung von 'string *' nach 'auto_ptr<string>' nicht möglich.
    

    In dem Bsp. (den Link von Dir) machen die das aber vom Prinzip her genau so, halt nur mit String:

    auto_ptr<String> f()
        {
          auto_ptr<String> result = new String;
          *result = "some value";
          cout << "some output";
          return result;  // rely on transfer of ownership;
                          // this can't throw
        }
    

    Was habe ich denn falsch gemacht ?



  • Taurin schrieb:

    Ist das eine übliche Optimierung, oder machen das nur wenige Compiler?

    g++ 3.2.2 kann es. Der Borland C++ Compiler 5.2 (von anno 1994 oder so) kann es auch, aber seltsamerweise nur, wenn man es so schreibt:

    string foo = getChar(42);

    anstatt string foo(getChar(42)); was 1 oder 2mal (je nachdem, ob es ein RVO- oder NRVO-Fall ist) den Kopierkonstruktor aufruft. Man kann wohl davon ausgehen, dass sie das inzwischen hinbekommen haben.

    Was mit MS ist, weiß ich nicht.


Anmelden zum Antworten