Sign in to follow this  
CyberSlag5k

Working with binary

Recommended Posts

I've never done anything involving reading/writing data in binary. However, I find myself with the need to do that very thing. I have a file (.stl) that contains triangle vertex and normal information I am going to read in and build an object from. I know the format of the file, so can I just use the C++ >> operator to grab the info? Or do I need to tell the compiler to convert from binary to something else first? Also, the file has an 80 byte header that contains no pertinant information to the object. I would imagine I can just use a seekg to skip past this section? Thanks in advance.

Share this post


Link to post
Share on other sites
Quote:
Original post by CyberSlag5k
so can I just use the C++ >> operator to grab the info? Or do I need to tell the compiler to convert from binary to something else first?


I think the >> operator is only for ASCII data. You have to use the read and write commands from the file stream library.

fin.read(Object, Size);
fout.write(Object, Size);

Quote:
Original post by CyberSlag5k
Also, the file has an 80 byte header that contains no pertinant information to the object. I would imagine I can just use a seekg to skip past this section?


You could do that or just use something like this:

char Buffer[80];
ifstream fin;
fin.open(filename);
fin.read(&Buffer,sizeof(Buffer));
<- more reading
fin.close();

EDIT: If you haven't already I suggest you read this article.

Share this post


Link to post
Share on other sites
Quote:
I know the format of the file, so can I just use the C++ >> operator to grab the info?


These are for formatted IO. Binary is unformatted IO. Use the stream's read() and write() member functions. Just make sure you only process POD-types in that way.

Quote:
Or do I need to tell the compiler to convert from binary to something else first?


On Microsoft platforms, it is necessary (but not sufficient!) to open the file in binary mode, to disable newline translation.

Quote:
Also, the file has an 80 byte header that contains no pertinant information to the object. I would imagine I can just use a seekg to skip past this section?


Yes. Though you should rely on the header to make sure you have a file in the correct format. Headers are rarely completely useless.

Share this post


Link to post
Share on other sites
I've played around with it a bit, and it seems the read function only accepts a character as a parameter. I initially tried this:

float temp;
fin.read(temp, 4);

but that didn't compile. Should I be reading in the entire set of data as a character buffer and then convert pieces of that buffer to floats? Seems like kind of a pain. Is there a better way? And aren't I going to lose some precision doing it that way? Or does specifying that 4 bytes should be read in prevent the loss of data?

[Edited by - CyberSlag5k on February 7, 2005 11:31:20 AM]

Share this post


Link to post
Share on other sites
iostreams are for formatted i/o only. Don't use them for binary operations. If you want to do that sort of thing, grab their stream buffer and manipulate that directly.

Share this post


Link to post
Share on other sites
A typical istream object's read() function expects a pointer to a character buffer. The quickest way to read a multi-byte variable (such as a 4-byte float) is to cast the address of that float as a char*, and tell read() to read 4 bytes into that character buffer.
float temp;
fin.read((char*)&temp, sizeof(temp));
This may have portability issues (little/big-endian), but what lazy programmer cares about those? [wink] I never know, 'cause I never work with anything other than Windows.

Share this post


Link to post
Share on other sites
Hmm...I'm getting some weird results. Here's roughly what I'm doing:


void loadListBinary()
{
LinkedList vertexList;
LinkedList normalList;
Vector normVector;
Vector tempVector;
char header[80];
char buffer[50];
int numFacets;

ifstream fin;
fin.open(fileName, ios::binary);

if(fin.is_open() == false)
exit(333);

fin.read(header, 80);
fin.read(buffer, 4);
numFacets = int(buffer[0]);

for(int i = 0; i < numFacets; i++)
{
fin.read(buffer, 50);

normVector.setVector(buffer[0], buffer[4], buffer[8]);
normalList.InsertItem(normVector);
fout << normVector << endl;

tempVector.setVector(buffer[12], buffer[16], buffer[20]);
vertexList.InsertItem(tempVector);
fout << tempVector << endl;

tempVector.setVector(buffer[24], buffer[28], buffer[32]);
vertexList.InsertItem(tempVector);
fout << tempVector << endl;

tempVector.setVector(buffer[36], buffer[40], buffer[44]);
vertexList.InsertItem(tempVector); fout << tempVector << endl << endl;
}
}





The format of the file is:


Any text such as the creator's name (80 bytes)
int equal to the number of facets in file (4 bytes)
//facet 1
float normal x (4 bytes)
float normal y
float normal z
float vertex1 x (4 bytes)
float vertex1 y
float vertex1 z
float vertex2 x
float vertex2 y
float vertex2 z
float vertex3 x
float vertex3 y
float vertex3 z
unused (padding to make 50-bytes)
//facet 2
float normal x
float normal y
float normal z
float vertex1 x
float vertex1 y
float vertex1 z
float vertex2 x
float vertex2 y
float vertex2 z
float vertex3 x
float vertex3 y
float vertex3 z
unused (padding to make 50-bytes)
//facet 3
...




Unfortunately, since the file is in binary it is difficult for me to see what values should be read in, but when I output the contents of my linked lists the first dozen or so vertices are all 0's and the rest values not what I think they should be. This makes me think I'm doing something quite wrong. Any help would be appreciated.

Share this post


Link to post
Share on other sites
Quote:
Original post by Trap
Change char buffer[50]; to int buffer[13]; and divide all indices by 4 then it should work.
No, then it'll interpret binary float values as int values, and things will still get screwed up.

There are a variety of ways to get this to work. One is just reading it straight into a float value as I specified above. Another, working with the code just posted, is to change the calls to setVector() to look like this:
normVector.setVector(*((float*)(buffer + 0)), *((float*)(buffer + 4)), *((float*)(buffer + 8)));

Somehow or another, you need to make the compiler treat some data as another type of data temporarily. This is easiest to accomplish using pointer casts. If you just cast normal data (such a buffer[0] (a char) to float), it'll just convert the first 8 bits to a float number, rather than interpretting 32 bits as though they represented a float. So you need to get the address of buffer[n] (which could be "&buffer[n]", or "buffer + n") and then treat that address as though it were an address to a float, not to a char. Or if you're reading straight into a float, as in my previous post, you need to make the address to the float act like an address to a char buffer. Make sense?

Share this post


Link to post
Share on other sites
Quote:
Original post by Agony
Quote:
Original post by Trap
Change char buffer[50]; to int buffer[13]; and divide all indices by 4 then it should work.
No, then it'll interpret binary float values as int values, and things will still get screwed up.

There are a variety of ways to get this to work. One is just reading it straight into a float value as I specified above. Another, working with the code just posted, is to change the calls to setVector() to look like this:
normVector.setVector(*((float*)(buffer + 0)), *((float*)(buffer + 4)), *((float*)(buffer + 8)));

Somehow or another, you need to make the compiler treat some data as another type of data temporarily. This is easiest to accomplish using pointer casts. If you just cast normal data (such a buffer[0] (a char) to float), it'll just convert the first 8 bits to a float number, rather than interpretting 32 bits as though they represented a float. So you need to get the address of buffer[n] (which could be "&buffer[n]", or "buffer + n") and then treat that address as though it were an address to a float, not to a char. Or if you're reading straight into a float, as in my previous post, you need to make the address to the float act like an address to a char buffer. Make sense?


Right, my concerns were that I would be attempting to store the data of a float in the sizoe of a character and I would lose part of the data. I tried modifying my original code based on your first post, but the shape drawn is garbage. Here's what I have now:


void loadListBinary()
{
Vector normVector;
Vector tempVector;
char float1, float2, float3;
char header[80];
char buffer[50];
int numFacets;

ifstream fin;
fin.open(fileName, ios::binary);

if(fin.is_open() == false)
exit(333);

fin.read(header, sizeof(header));
fin.read((char*)&numFacets, sizeof(numFacets));

for(int i = 0; i < numFacets; i++)
{
fin.read((char*)&float1, sizeof(float1));
fin.read((char*)&float2, sizeof(float2));
fin.read((char*)&float3, sizeof(float3));
normVector.setVector(float1, float2, float3);
normalList.InsertItem(normVector);

fin.read((char*)&float1, sizeof(float1));
fin.read((char*)&float2, sizeof(float2));
fin.read((char*)&float3, sizeof(float3));
tempVector.setVector(float1, float2, float3);
vertexList.InsertItem(tempVector);

fin.read((char*)&float1, sizeof(float1));
fin.read((char*)&float2, sizeof(float2));
fin.read((char*)&float3, sizeof(float3));
tempVector.setVector(float1, float2, float3);
vertexList.InsertItem(tempVector);

fin.read((char*)&float1, sizeof(float1));
fin.read((char*)&float2, sizeof(float2));
fin.read((char*)&float3, sizeof(float3));
tempVector.setVector(float1, float2, float3);
vertexList.InsertItem(tempVector);

}

vertexList.ResetList();
normalList.ResetList();
fin.close();
}



The shape just comes up garbage. Am I doing somewthing wrong?

Share this post


Link to post
Share on other sites
Is there some way I can just view the file in ASCII so I at least know what isn't getting loaded properly? I can see what my program is recieving as input, but I don't know where/how that breaks with what it should be getting (expect in that the shape it draws is an absolute mess).

Share this post


Link to post
Share on other sites
Quote:
From here:

... using the get() and put()/write() member functions still aren't guaranteed to help you. These are "unformatted" I/O functions, but still character-based.

... "Use streambufs, that's what they're there for." While not trivial for the beginner, this is the best of all solutions. The streambuf/filebuf layer is the layer that is responsible for actual I/O. If you want to use the C++ library for binary I/O, this is where you start.


Based on this, I'm assuming this is the correct way to do binary I/O:


#include <iostream>
#include <fstream>
#include <stdexcept>

void doWriting(std::streambuf &buffer)
{
// Move write position to start of file.
buffer.pubseekpos(0, std::ios_base::out);

// Write all the possible bytes, 0 through 255:
for(int byte = 0; byte < 256; ++byte)
if(buffer.sputc(byte) == std::streambuf::traits_type::eof())
throw std::runtime_error("Got EOF writing byte to buffer.");

// Write some raw data:

// Note: Dumping raw data from memory into a file isn't the proper way to do this,
// and may not be readable on other computers, or even the same computer with a
// different compiler, or even the same compiler invoked with different options.

float number1 = 42.0f;
double number2 = 54.0;
long number3 = 31337;
short number4 = 1337;

if(sizeof number1 != buffer.sputn(reinterpret_cast<char *>(&number1), sizeof number1))
throw std::runtime_error("Got EOF writing number1 to buffer.");

if(sizeof number2 != buffer.sputn(reinterpret_cast<char *>(&number2), sizeof number2))
throw std::runtime_error("Got EOF writing number2 to buffer.");

if(sizeof number3 != buffer.sputn(reinterpret_cast<char *>(&number3), sizeof number3))
throw std::runtime_error("Got EOF writing number3 to buffer.");

if(sizeof number4 != buffer.sputn(reinterpret_cast<char *>(&number4), sizeof number4))
throw std::runtime_error("Got EOF writing number4 to buffer.");

// Make sure everything has gotten written to whatever the heck it is we're writing to.
buffer.pubsync();
}

void doReading(std::streambuf &buffer)
{
// Move read position to start of file.
buffer.pubseekpos(0, std::ios_base::in);

std::cout << "The numbers are: ";

// Read those bytes back:
for(int byte = 0; byte < 256; ++byte)
std::cout << buffer.sbumpc() << ' ';

float number1;
double number2;
long number3;
short number4;

// Read those numbers back:
if(sizeof number1 != buffer.sgetn(reinterpret_cast<char *>(&number1), sizeof number1))
throw std::runtime_error("Got EOF reading number1.");

if(sizeof number2 != buffer.sgetn(reinterpret_cast<char *>(&number2), sizeof number2))
throw std::runtime_error("Got EOF reading number2.");

if(sizeof number3 != buffer.sgetn(reinterpret_cast<char *>(&number3), sizeof number3))
throw std::runtime_error("Got EOF reading number3.");

if(sizeof number4 != buffer.sgetn(reinterpret_cast<char *>(&number4), sizeof number4))
throw std::runtime_error("Got EOF reading number4.");

std::cout << "\nnumber1 is: " << number1;
std::cout << "\nnumber2 is: " << number2;
std::cout << "\nnumber3 is: " << number3;
std::cout << "\nnumber4 is: " << number4 << std::endl;
}

int main()
{
// Create our file stream.
std::fstream file("numbers.bin", std::fstream::binary | std::fstream::in | std::fstream::out | std::fstream::trunc);

try
{
if(!file.good())
throw std::runtime_error("Unable to open the file.");

// Get a reference to the file's buffer.
std::streambuf &buffer(*file.rdbuf());

doWriting(buffer);
doReading(buffer);
}

catch(std::exception &error)
{
std::cerr << "Something bad happened: " << error.what() << '\n';
}

return 0;
}



Feel free to make any suggestions to that, I don't have much experience with that sort of thing.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this