Warning: Undefined array key "p" in /home/clients/119990c2465ec8673b725b4ed2ffc513/sites/informatikboard.ch/includes/vb5/template.php(404) : eval()'d code on line 794 Binär File einlesen - iB - InformatikBoard.ch - Benutzer helfen Benutzern

Ankündigung

Einklappen
Keine Ankündigung bisher.

Binär File einlesen

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Problem: Binär File einlesen

    Hallo Zusammen

    Vor kurzem habe ich begonnen ein Programm in C++ zu schreiben, welches unter anderem eine Bitmap Datei einlesen soll.
    Das Ziel ist es Daten aus dem Fileheader und auch die Farbwerte einzelner Pixel in Variablen zwischen zu speichern. Ich habe herausgefunden, dass das mit ifstream funktioniert.

    Problem: die Funktion "read" von ifstream scheint nur daten in char arrays einzulesen.

    Mein Code sieht bisher etwa so aus:
    Code:
    void bmpRead()
    {
    	ifstream file(picture.bmp, ios::in | ios::binary);
    	if(file.is_open())
    	{
    		unsigned int bfOffBits;
    		file.read(bfOffBits, 10);      
    	}
    		
    }
    Der Aufbau von Bitmap Dateien habe ich hier gefunden.

    bfOffBits ist laut diesem Artikel ein unsigned int mein Compiler sagt aber, dass als Argument von file.read() ein char* erwartet wird.

    Kennt sich jemand damit aus? Ich habe schon ewig nach einer Lösung gegoogelt.

    Danke und Gruss

  • #2
    AW: Binär File einlesen

    Der Compiler hat immer recht .

    In der Definition von ifstream::read sieht man, dass der erste Parameter ein `char*` sein muss.

    Treten wir einen Schritt zurück. Wenn man mit Eingabe/Ausgabe arbeitet tritt man immer wieder auf das gleiche Muster. Bei den Meisten Funktionen welche "read" oder "write" enthalten, wird mindestens eine Adresse und eine Zahl erwartet. Die Adresse zeigt auf einen Speicherbereich. In diesem Speicherbereich wird entweder hineingeschrieben (im Falle von ifstream::read läuft das so [Datei] -> [Speicher]) oder gelesen (bei ifstream::write so [Speicher] -> [Datei]). Der `char*` Parameter ist eben diese Adresse auf den Speicherbereich.

    Die Zeile "file.read(bfOffBits, 10);" versucht also den Wert einer "unsigned int" Variable als Speicheradresse zu übergeben. Theoretisch könnte man den Compilerfehler mit einem Cast zum "verschwinden bringen" - etwa so: "file.read(reinterpret_cast<char*>(bfOffBits), 10);". Dies ist jedoch keine Lösung des Problems weil die Adresse welche nun übergeben wird der Wert der variable bfOffBits ist (welcher nicht zugewiesen, also undefiniert, ist).

    Nehmen wir mal an der Wert von bfOffBits ist 42 aber die Adresse, wo bfOffBits im Speicher liegt, ist 23. Mit einem einfachen Cast erhält "read" die Adresse 42 und versucht dort hin zu schreiben im besten Fall stürzt dein Program ab (Access Violation, Segmentation Fault) im schlechtesten Fall überschreibst Du irgendwelche Daten und das Program verhält sich dann merkwürdig (viel Spass beim Debuggen). Was Du eigentlich möchtest, ist das Read bei der Adresse 23 (also dem Speicherplatz von bfOffBits) schreibt.

    Wie erhält man also die Adresse in der "bfOffBits" gespeichert ist? Ganz einfach, mit dem adress-of-Operator &. Den Cast braucht es aber trotzdem weil die Adresse einer unsigned int-Variable als "unsigned int *" zurückgegeben wird.

    Die Zeile müsste dann also etwa so aussehen: "file.read(reinterpret_cast<char*>(&bfOffBits) , 10);". Sind wir damit schon fertig? Nein!

    Wenden wir uns dem zweiten Parameter von ifstream::read zu. Dieser gibt die Grösse des übergebenen Speicherbereichs an. Die grösse eines "unsigned int" ist 4 bytes. Es werden 10 bytes gelesen -> Access Violation/Segmentation fault/Buffer overflow.

    Also gut: "file.read(reinterpret_cast<char*>(&bfOffBits) , 4);". Diese Zeile speichert die Ausgabe in der richtigen Adresse und die Grösse des Speicherbereichs ist korrekt angegeben (4 bytes). Man kann das noch etwas flexibler gestalten mit dem "sizeof" Operator, dieser gibt die Grösse einer Variabel zurück: file.read(reinterpret_cast<char*>(&bfOffBits), sizeof(bfOffBits));

    Nun würde das Program zwar keine Zugriffsverletzung produzieren, aber der richtige Wert wird damit immer noch nicht eingelesen. Das Problem ist das der Filepointer am Anfang der Datei steht, während Du eigentlich am Offset 10 interessiert bist (darum hast Du wahrscheinlich "10" beim zweiten Parameter von ifstream::read angegeben). Dies kannst Du auf zwei Arten lösen:

    1. Du liest einfach alles bis zum Offset 10 ein, bevor du bfOffBits einliest.
    2. Du verschiebst den Filepointer auf Offset 10 und liest dann bfOffBits.

    Bei Punkt 1 würde es etwa so aussehen:
    Code:
    ifstream file("picture.bmp", ios::in | ios::binary);
    
    if(file.is_open()) {
        char bfType[2];
        unsigned int bfSize;
        unsigned int bfReserved;
        unsigned int bfOffBits;
    
        // Arrays werden "automatisch" als Pointer gecastet 
        // (eigentlich ist ein Array in C/C++ ein Pointer)
        // Bei Pointern gibt "sizeof" immer die Grösse des 
        // Pointers zurück (also entweder 4 - 32 bit oder 8 - 64 bit)
        // und nicht die grösse des Speicherbereichs auf den der Pointer
        // zeigt. In diesem speziellen Fall gibt sizeof jedoch die richtige
        // grösse zurück (2), weil bfType dem compiler "bekannt" ist.
        // Falls dies verwirrend ist, für später vormerken und einfach 
        // die Grösse als Zahl fest angeben, also `file.read(bfType, 2);`
        file.read(bfType, sizeof(bfType));
                
        file.read(reinterpret_cast<char*>(&bfSize), sizeof(bfSize));
        file.read(reinterpret_cast<char*>(&bfReserved), sizeof(bfReserved));
        file.read(reinterpret_cast<char*>(&bfOffBits), sizeof(bfOffBits));
    
        cout << "Header: " << bfType[0] << bfType[1]
            << " - bfSize: " << bfSize
            << " - bfOffBits: " << bfOffBits
            << endl;
    }
    Es werden also die führenden Felder eingelesen (2 bytes für BM, 4 bytes für die Grösse und 4 Bytes für das "bfReserved"-Feld = 2 + 4 + 4 = 10 bytes), und der Filepointer ist damit beim richtigen Offset 10.

    Die zweite Lösung, mit dem Setzen des Filepointers sieht etwas einfacher aus. Man verwendet ifstream::seekg um den Filepointer zu setzen.
    Code:
    ifstream file("picture.bmp", ios::in | ios::binary);
    
    if(file.is_open()) {
        unsigned int bfOffBits;
    
        file.seekg(10, ios::beg);
        file.read(reinterpret_cast<char*>(&bfOffBits), sizeof(bfOffBits));
    
        cout << "Header: bfOffBits: " << bfOffBits
            << endl;
    
        // Theoretisch geht's jetzt weiter -  um zu den Bits zu springen kannst
        // Du ebenfalls seekg verwenden
        file.seekg(bfOffBits, ios::beg);
    
       // ab hier bits einlesen....
    }
    Ich hoffe das hilft Dir etwas weiter. Am Anfang ist es immer etwas mühsam bis man das mit dem Speicher und den Adressen kapiert hat.

    Wenn Du eine IDE wie Visual Studio mit einem Debugger verwendest, lerne mit den Debugger umzugehen, damit kann man solche Probleme einfacher lösen.

    Kommentar

    Lädt...
    X