Leitura de arquivo binário gravado em C

Alguém pode me ajudar???

Estou tendo dificuldades em ler um arquivo binário que foi gravado por um programa feito em C. Os campos texto eu consigo ler, mas os campos int, double e outros tipos numéricos não lê o mesmo valor que foi gravado.

Se alguém puder me ajudar desde já agradeço.

Isso foi respondido em algum tópico antes do “crash” do GUJ…
Acontece que o int do C não ocupa necessariamente o mesmo número de bits do int do Java. Tente descobrir qual o tamanho do int do seu compilador C e então use um readByte no java, então, a cada vez que acumular o nº de bytes certo, jogue o valor acumulado em um int.

o int do C++ (Win32) ocupa 4 bytes e o do JAVA também ocupa 4 bytes

Então verifique os outros tipos: double, etc…

Esse tipo de problema acontece porque o dado começa a ser lido na posição errada do arquivo, ou termina de ser lido na posição errada do arquivo (ou os dois).

O valor booleano, por exemplo, em Java, ocupa nº de bytes diferentes dependendo da máquina virtual que está sendo utilizada.

Será que o int não está sendo pego a partir de um resto do String, como um caractere ‘\0’, por exemplo? Se não me engano o C e o Java não interpretam o ‘\0’ da mesma forma.

Por exemplo, suponha que você tenha um arquivo que foi gerado pelo seguinte programa C++:

#define _CRT_SECURE_NO_DEPRECATE
#define _USE_MATH_DEFINES
#include <cstdio>
#include <cmath>
using namespace std;
int main (int argc, char *argv[]) {
    FILE* f = fopen ("teste.bin", "wb");
    double dbl = M_PI; // 3.14159265358979323846
    float flt = M_E; // 2.71828182845904523536
    fwrite (&dbl, sizeof(dbl), 1, f);
    fwrite (&flt, sizeof(flt), 1, f);
    fclose (f);
}

Ele pode ser lido pelo seguinte programa Java:

import java.io.*;

class DoubleCJava {
    public static void main(String[] args) throws Exception {
        DataInputStream dis = new DataInputStream (new FileInputStream ("teste.bin"));
        long _dbl = dis.readLong(); // lê 8 bytes
        int _flt = dis.readInt(); // lê 4 bytes
        dis.close();
        _flt = ( (_flt >>> 24 & 0x000000FF) 
               | (_flt >>>  8 & 0x0000FF00) 
               | (_flt <<   8 & 0x00FF0000) 
               | (_flt <<  24 & 0xFF000000));
        _dbl = ( (_dbl >>> 56 & 0x00000000000000FFL)
               | (_dbl >>> 40 & 0x000000000000FF00L)
               | (_dbl >>> 24 & 0x0000000000FF0000L)
               | (_dbl >>>  8 & 0x00000000FF000000L)
               | (_dbl <<   8 & 0x000000FF00000000L)
               | (_dbl <<  24 & 0x0000FF0000000000L)
               | (_dbl <<  40 & 0x00FF000000000000L)
               | (_dbl <<  56 & 0xFF00000000000000L));
        float flt = Float.intBitsToFloat(_flt);
        double dbl = Double.longBitsToDouble(_dbl);
        System.out.println (dbl); // imprime 3.141592653589793
        System.out.println (flt); // imprime 2.7182817
    }
}

Como funcionam esses deslocamentos binários?

Eu não poderia imprimir o sizeof(flt), usar um byte[] e readByte() e jogar o valor em um float quando atingisse o tamanho do sizeof?

Esse monte de deslocamentos é porque a ordem dos bytes em máquinas compatíveis com Intel (little-endian) é ao contrário da ordem que o Java espera os bytes em um DataInput/OutputStream (big-endian).
E de qualquer maneira a função de conversão da representação IEEE de um float ou double requer o uso de um int ou um long.

Hum… muito interessante, eu não sabia disso… :-o

Se o arquivo foi gerado em uma máquina Sparc (Sun, Fujitsu), em uma HP PA-RISC ou em uma IBM PowerPC (ou então em um Mac não-Intel), então não é necessário fazer esses shifts todos (são big-endian).
Só as máquinas Intel (e se você usa Digital Alpha ou Intel Itanium em modo little-endian) é que pensam “ao contrário”.
O problema é que a maior parte dos computadores usam chips que são Intel ou compatíveis…

Que a Intel não segue o padrão eu sabia.
Mas eu não havia parado para associar que o Java seguiria o padrão (Big Endian) o que, claro, é natural…
É que a gente vai ficando tão acostumado com ambientes Intel (e Micro$oft)…
De qualquer forma eu nunca precisei trocar informações binárias entre C e Java, o mais próximo que eu já cheguei disso foram pequenas manipulações em um arquivo .wav, sendo que formatos de áudio também podem obedecer a um dos dois padrões (big ou little endian)…

Em Java a ordem dos bytes é importante em:

  • DataInputStream/DataOutputStream
  • Serialização (que indiretamente usa essas rotinas readInt/readShort/readDouble/readFloat/readLong).

Se o arquivo original foi criado em uma máquina big-endian, pode-se usar diretamente readFloat e readDouble em vez de usar essa “gambiarra” que mostrei acima.

É… basicamente o que isso faz é desinverter o código binário, certo?

Inverte 32 bits (4 bytes) para o float e 64 bits (8 bytes) para o double.

Inversão por deslocamentos…

Ficou claro!

obs: como não existe uma utilidade nativa para isso?

Ou seja, esqueci de efetuar um "refactoring" no código acima para evitar esses "shifts" sem explicação.

import java.io.*;

class DoubleCJava {
    /** 
     * Inverte os bytes de um int 
     */
    public static int inverteBytes (int x) {
        return ( (x >>> 24 & 0x000000FF) 
               | (x >>>  8 & 0x0000FF00) 
               | (x <<   8 & 0x00FF0000) 
               | (x <<  24 & 0xFF000000));
    }
    /** 
     * Inverte os bytes de um long
     */
    public static long inverteBytes (long x) {
        return ( (x >>> 56 & 0x00000000000000FFL)
               | (x >>> 40 & 0x000000000000FF00L)
               | (x >>> 24 & 0x0000000000FF0000L)
               | (x >>>  8 & 0x00000000FF000000L)
               | (x <<   8 & 0x000000FF00000000L)
               | (x <<  24 & 0x0000FF0000000000L)
               | (x <<  40 & 0x00FF000000000000L)
               | (x <<  56 & 0xFF00000000000000L));
    }
    /** 
     * Este programa lê um double e um float que foram gravados por um programa em C
     * em ambiente Intel (little-endian).
     */    
    public static void main(String[] args) throws Exception {
        DataInputStream dis = new DataInputStream (new FileInputStream ("teste.bin"));
        long _dbl = dis.readLong();
        int _flt = dis.readInt();
        dis.close();
        _dbl = inverteBytes (_dbl);
        _flt = inverteBytes (_flt);
        double dbl = Double.longBitsToDouble(_dbl);
        float flt = Float.intBitsToFloat(_flt);
        System.out.println (dbl); // imprime 3.141592653589793
        System.out.println (flt); // imprime 2.7182817
    }
}

Está em tempo! :smiley: