Im currently trying to replace a text based file in my application with a binary one. Im just doing some early tests so the code isn't exactly safe but I'm having problems with the data.
When trying to read out the data it gets about half way before it starts coming back with incorrect results.
Im creating the file in c++ and my client application is c#. I think the problem is in my c++ (which I haven't used very much)
Where the problem is at the moment is I have a vector of a struct that is called DoubleVector3 which consists of 3 doubles
struct DoubleVector3 {
double x, y, z;
DoubleVector3(std::string line);
};
Im currently writing the variables individually to the file
void ObjElement::WriteToFile(std::string file) {
std::ofstream fileStream;
fileStream.open(file); //, ios::out | ios::binary);
// ^^problem was this line. it should be
// fileStream.open(file, std::ios_base::out | std::ios_base::binary);
fileStream << this->name << '\0';
fileStream << this->materialName << '\0';
int size = this->vertices.size();
fileStream.write((char*)&size,sizeof(size));
//i have another int written here
for (int i=0; i<this->vertices.size(); i++) {
fileStream.write((char*)&this->vertices[i].x, 8);
fileStream.write((char*)&this->vertices[i].y, 8);
fileStream.write((char*)&this->vertices[i].z, 8);
}
fileStream.close();
}
When I read the file in c# the first 6 sets of 3 doubles are all correct but then I start getting 0s and minus infinities
Am I doing anything obviously wrong in my WriteToFile code?
I have the file uploaded on mega if anyone needs to look at it
https://mega.co.nz/#!XEpHTSYR!87ihtCfnGXJJNn13iE6GIpeRhlhbabQHFfN88kr_BAk
(im writing the name and material in first then the number of vertices before the actual list of vertices)
Small side question - Should I delimit these doubles or just add them in one after the other?
To store binary data in a stream, you must add std::ios_base::binary to the stream's flags when opening it. Without this, the stream is opened in text mode and line-ending conversions can happen.
On Windows, line-ending conversions mean inserting a byte 0x0D (ASCII for carriage-return) before each 0x0A byte (ASCII for line-feed). Needless to say, this corrupts binary data.
Related
Being new to C++, I am confused about what << and put() means while using ofstream to write to a text file. I tried to experiment with the two following styles as follows:
Approach 1:
void writeTester() {
std::ofstream oFile("Resources/tst.txt", std::ios::out | std::ios::trunc);
std::vector<int> v{ 1,2,3,4 };
for (int i = 0;i < 4;i++) {
oFile.put(v[i]);
//casting to character pointer and writing also produced similar result
//oFile.put(*(char*)&v[i]);
}
oFile.close();
}
Approach 2:
void writeTester() {
std::ofstream oFile("Resources/tst.txt", std::ios::out | std::ios::trunc);
std::vector<int> v{ 1,2,3,4 };
for (int i = 0;i < 4;i++) {
oFile << v[i];
}
oFile.close();
}
While Approach 2 wrote the expected result to file (1234), Approach 1 wrote some garbage value to the file.
What is the difference between the 2 styles, and when to use which one? Also, what is the correct way to use Approach 1 to have "1234" as the output written to the file?
Simply put, using the '<<' operator means certain overloads can be used, which means writing integer values (as in your case) will mean they're converting to their string representations before being written to the file.
Using put, however, will write a single byte to the stream. This means your 4 byte int will be truncated to a single byte, thus writing nonsense data to your file.
Here's the documentation for ofstream::put.
And here's the documentation for ofstream.
For completion, and to spark your interest, here's an ASCII table, where you can look up the values you were writing to the file. Depends on the byte order (endianness) of your machine, which char is written. On LE machines it should be NUL, but I get confused myself sometimes and mix up the byte orders in my head so please take that last sentence with a grain of salt.
I need to read file in parts ( for example by 4 bytes) and then increment numbers in files by one and then write back;
this part only fills in file by 1; How to increase this number on 1?
void Prepare()
{
//ifstream fileRead("\FILE", ios::in | ios::binary);
ofstream fileOut("\FILE.bin", ios::out | ios::binary);
int count = 10485760;
for (int i = 0; i < count-1; i++)
{
fileOut << 1;
}
fileOut.close();
}
If I understand your question, you need to read the file then write it out, changing the data. You can't really do it the way you've started.
There are two basic ways to do this. You can read the entire file into memory, then manipulate the memory, close the file, open it again for output this time (truncating it) and write it back out. This is easiest, but I don't think it's the approach you're looking for.
The other choice is to manipulate the file in place. That's trickier, but not that hard. You need to read about random access I/O (input/output). If you google for c++ random access file you'll get some good hits, but I'll show you a little bit.
// Open the file.
std::ifstream file{"file.dat"};
// Jump to a particular location in the file. Beginning is 0.
file.seekg(128);
// Read 4 bytes
char bytes[4];
file.read(bytes, 4);
// Manipulate it (more below)
int number = bytesToInt(bytes);
++number;
intToBytes(number, bytes);
// Seek again
file.seekg(128);
file.write(bytes, 4);
So the only remaining trick is that you have to convert the bytes to a number and then back into bytes. Due to endianness, it's not safe to read directly into the number. You also need to know the endianness of the data in the file. That's a separate topic you can look up if you're not already familiar with it.
(Specifically, you need to implement those two methods after verifying how the data is stored in your file.)
There may be other ways to do this, but the key to this method is the random access file.
Good day,
I'm working on a program, which reads in a binary file (functions for read in are given and work) and then write the modified information back in another binary file. (I'm changing between two formats.) If I use std::cout to give out the numbers to the console right before (or after) I use ofstream to write them in a file. I get out the numbers I anticipate, but if I know input them into ofstream via << or write and then look at the contents of the file. (The conversion from this format to the original one exists and I can use it.) Then I get carbage.
struct Record {
unsigned latch;
float e, x, y, u, v, wt;}
I read in the given file and modify the numbers I read in. (The following is part of the main-function). Header h is defined and there exists a default constructor. phsp.read and copy are given and work. Reclen corresponds to the Record length and ensures, that the header is written right, because it can be different for different inputs.
std::ofstream outs;
outs.open(outfile, std::ios::out | std::ios::trunc);
Header h;
outs.write((char*) &h, reclen - 5);
for (int i=0; i<nMax; ++i){
phsp.read(mp);
copy(mp, ep);
Record rec;
Modify numbers and putting them into rec, which is a struct and given above. Rec should be written into the file
outs<<rec.latch<<rec.e<<rec.x<<rec.y<<rec.u<<rec.v<<rec.wt;
outs.write((char*) &rec.latch, 4);outs.write((char*) &rec.e, 4);
outs.write((char*) &rec.x, 4); outs.write((char*) &rec.y, 4);
outs.write((char*) &rec.u, 4);outs.write((char*) &rec.v, 4);
outs.write((char*) &rec.wt, 4);
outs.write((char*) &rec, reclen);
}
Not one of this writes, what I want into the file, but if I give out the numbers through std::cout and the console. I get the expected numbers.
std::cout<<rec.latch<<' '<<rec.e<<' '<<rec.x<<' '<<rec.y<<' '<<rec.u<<' '<<rec.v<<
' '<<rec.wt<<'\n';
I also checked the outs stream with good() and I also checked that the file is open. I can also write into the file. Also the write and the << output don't give the same output (could be expected). I checked the size of the rec.i and it also corresponds to the size of the structs.
At the end I change the header again, because I have now new information.
outs.seekp(5);
outs.write((char*) &h, reclen - 5);
outs.close();
There I also have the same problem, with false numbers. I noticed that a number of 0 corresponds to 0, but the number 1 is converted to 16777216 for a unsigned int number.
I hope you can help me, I have no idea what is wrong.
You need to decide whether your new file format is supposed to use binary or text representation for data.
Typically when you use ofstream::write() method you're saving your data in binary format, which means it will be represented as an array of bytes with the length equal to the data structure you're writing. Therefore what you will find in the file will not be the number but its binary representation which generally looks like rubbish if seen as text.
You may see the number by using a hex editor, or you can read it back and then interpret it as a type of your choice. For example:
float number;
ifs.read(&number, sizeof(float); // read in the 4 bytes from the file into a float
This is an equivalent of doing:
char* buffer = new char[sizeof(float)];
ifs.read(buffer, sizeof(float)];
float f = *(reinterpret_cast<float*>(buffer));
If you printed out the contents of the buffer before casting it, you'd see the same rubbish you see in your file. Note that reading and writing binary data requires ios::binary flag set.
operator<< is typically used for writing data as text. It is less efficient in terms of space and speed, but has an advantage of being human readable. Writing data as text is as simple as printing it to the screen, e.g.
std::ofstream ofs;
ofs.open("file.txt");
float f = 1.337f;
ofs << "My float: " << f;
std::cout << "My float: " << f;
If you use above code in the same scope, there is no way for the contents in your file to be different from what's shown on your screen.
Finally make sure that your variables are initialized before using them, as in the code you've posted you're not doing it. E.g.
Header h; // <-- this probably contains rubbish until initialized with some values
outs.write((char*) &h, reclen - 5); // <-- writes rubbish
For converting an ordinary text file into binary and then convert that binary file back to a text file so that the first text file equals with the last text file, I have wrote below code.
But the bintex text file and the final text file aren't equal. I don't know which part of code is incorrect.
Input sample ("bintex") contains this: 1983 1362
The result ("final") contains this: 959788084
which of course are not equal.
#include <iostream>
#include <fstream>
using namespace std;
int main() try
{
string name1 = "bintex", name2 = "texbin", name3 = "final";
ifstream ifs1(name1.c_str());
if(!ifs1) error("Can't open file for reading.");
vector<int>v1, v2;
int i;
while(ifs1.read(as_bytes(i), sizeof(int)));
v1.push_back(i);
ifs1.close();
ofstream ofs1(name2.c_str(), ios::binary);
if(!ofs1) error("Can't open file for writting.");
for(int i=0; i<v1.size(); i++)
ofs1 << v1[i];
ofs1.close();
ifstream ifs2(name2.c_str(), ios::binary);
if(!ifs2) error("Can't open file for reading.");
while(ifs2.read(as_bytes(i), sizeof(int)));
v2.push_back(i);
ifs2.close();
ofstream ofs2(name3.c_str());
if(!ofs2) error("Can't open file for writting.");
for(int i=0; i<v2.size(); i++)
ofs2 << v2[i];
ofs2.close();
keep_window_open();
return 0;
}
//********************************
catch(exception& e)
{
cerr << e.what() << endl;
keep_window_open();
return 0;
}
What is this?
while(ifs1.read(as_bytes(i), sizeof(int)));
It looks like a loop that reads all input and throws it away. The line afterward suggests that you should be using braces instead of a semicolon there, and doing the write in the block.
Your read and write operations aren't symmetric.
ifs1.read(as_bytes(i), sizeof(int))
grabs 4 bytes, and dumps the values into the char* its passed.
ofs1 << v1[i];
output the integer in v[i] as text. Those are very very different formats.
If you used >> to read you would have a lot more success.
To expound, the first read might look like this {'1','9','8','3'}, which I would guess would be the 959788084 you are seeing when you pun it to an int. Your second read would be {' ','1','3','6'}, like not what you'd hoped for either.
It's not clear (to me, at least), what you are trying to do.
When you say that the orginal file contains 1983 1262, what do
you really mean? That it contains two four byte integers, in
some unspecified format, whose values are 1983 and 1262? If so,
the problem is probably due to your machine not using the same
format. You cannot, in general, just read bytes (using
istream::read) and expect them to mean anything in your
machine's internal format. You have to read the bytes into
a buffer, and unformat them, according to the format with which
they were written.
Of course, opening a stream in binary mode doesn't mean that
the actual data are in some binary format; it just affects
things like how (or more strictly speaking, whether) line
endings are encoded, and how end of file is recognized.
(Strictly speaking, a binary file is not divided into lines. It
is just a sequence of bytes. Of course, some of those bytes
might have values that you, in your program, interpret and new
line characters.) If your file actually contains nine bytes
with characters corresponding to "1983 1362", then you'll have
to parse them as a text format, even if the file is written in
binary. You can do this by reading the entire file into
a string, and usingstd::istringstream; _or_, on most common
systems (but not necessarily on all exotics) by using>>` to
read, just as you would with a text file.
EDIT:
Just a simple reminder: you don't show the code for as_bytes,
but I'm willing to guess that there's a reinterpret_cast in
it. And any time you have to use a reinterpret cast, you can be
very sure that what you're doing isn't portable, and if it's
supposed to be portable, you're doing it wrong.
Edit: I'm trying to convert a text file into bytes. I'm not sure if the code is turning it into bytes or not. Here is the link to the header so you can see the as_bytes function.
link
#include "std_lib_facilities.h"
int main()
{
cout << "Enter input file name.\n";
string file;
cin >> file;
ifstream in(file.c_str(), ios::binary);
int i;
vector<int> bin;
while(in.read(as_bytes(i), sizeof(int)))
bin.push_back(i);
ofstream out(file.c_str(), ios::out);
for(int i = 0; i < bin.size(); ++i)
out << bin[i];
keep_window_open();
}
Note that now the out stream just outputs the contents of the vector. It doesn't use the write function or the binary mode. This converts the file to a large line of numbers - is this what I'm looking for?
Here is an example of the second code's file conversion:
that guy likes to eat lots of pie (not sure if this was exact text)
turns to
543518319544825700191924850016351970295432362115448292821701667182186922608417526375411952522351186935715718643976841768956006
The reason your first method didn't change the file is because all files are stored in the same way. The only "difference" between text files and binary files is that text files contain only bytes that can be shown as ASCII characters, while binary files* have a much more random variety and order of bytes. So you are reading bytes in as bytes and then outputting them as bytes again!
*I'm including Unicode text files as binary, since they can have multiple bytes to denote one character point, depending on the character point and the encoding used.
The second method is also fairly simple. You are reading in the bytes, as before, and storing them in integers (which are probably 4 bytes long). Then you are just printing out the integers as if they are integers, so you are seeing a string of numbers.
As for why your first method cut off some of the bytes, you're right in that it's probably some bug in your code. I thought it was more important to explain what the ideas are in this case, rather than debug some test code.