I'll post my code here in the case the last answer is not clear.
I'm not using std. I really think std overloaded operators makes things very complicated to read and write.
The class below handles both reading and writing of binary files. The class manages its own buffer only when is used as a read-only stream. (You should probably break this into small classes later.)
The header:
#ifndef __BYTE_STREAM__
#define __BYTE_STREAM__
#include "..\IRStdLib\CString.h"
#include "CIStream.h"
class CIByteStream : public CIStream {
public :
CIByteStream(unsigned char* _pbBytes, unsigned int _ui32BytesCount);
~CIByteStream();
virtual I_STREAM_ERRORS LoadBytesFromFile(const CString& _sFilePath);
virtual I_STREAM_ERRORS WriteBytesInFile(const CString& _sFilePath) const;
const unsigned char* GetBuffer() const;
unsigned int GetBufferSize() const;
virtual void ReadBytes(unsigned char** _pbOut, unsigned int _ui32BytesCount) const;
virtual unsigned int ReadInt32() const;
virtual float ReadFloat() const;
virtual void WriteBytes(const unsigned char* _pbBytes, unsigned int _ui32BytesCount);
virtual void WriteInt32(unsigned int _ui32Value);
virtual void WriteFloat(float _fValue);
protected :
unsigned int m_ui32BufferSize;
unsigned char* m_pbBuffer;
mutable unsigned char* m_pbCurBufferPtr;
};
inline const unsigned char* CIByteStream::GetBuffer() const {
return m_pbBuffer;
}
inline unsigned int CIByteStream::GetBufferSize() const {
return m_ui32BufferSize;
}
#endif // #ifndef __BYTE_STREAM__
The .cpp:
#include "CIByteStream.h"
#include <cstring>
#include <cstdio>
CIByteStream::CIByteStream(unsigned char* _pbBytes, unsigned int _ui32BytesCount) :
m_pbBuffer(NULL),
m_pbCurBufferPtr(_pbBytes),
m_ui32BufferSize(_ui32BytesCount) {
}
CIByteStream::~CIByteStream() {
::free(m_pbBuffer);
}
CIStream::I_STREAM_ERRORS CIByteStream::LoadBytesFromFile(const CString& _sFilePath) {
FILE* pfStream = NULL;
::fopen_s(&pfStream, (char*) _sFilePath.Chars(), "rb");
if (!pfStream) {
return CIStream::IE_FAILED;
}
::fseek(pfStream, 0L, SEEK_END);
unsigned int ui32Size = ::ftell(pfStream);
if (!ui32Size) {
::fclose(pfStream);
return CIStream::IE_EMPTY;
}
m_pbBuffer = reinterpret_cast<unsigned char*>(::malloc(ui32Size));
::fseek(pfStream, 0L, SEEK_SET);
::fread(m_pbBuffer, 1, ui32Size, pfStream);
::fclose(pfStream);
m_pbCurBufferPtr = m_pbBuffer;
m_ui32BufferSize = ui32Size;
return CIStream::IE_NONE;
}
CIByteStream::I_STREAM_ERRORS CIByteStream::WriteBytesInFile(const CString& _sFilePath) const {
FILE* pfStream = NULL;
::fopen_s(&pfStream, (char*)_sFilePath.Chars(), "wb");
if (!pfStream) {
return CIStream::IE_FAILED;
}
::fwrite(m_pbBuffer, sizeof(unsigned char), m_ui32BufferSize, pfStream);
::fclose(pfStream);
return CIStream::IE_NONE;
}
void CIByteStream::ReadBytes(unsigned char** _pbOut, unsigned int _ui32Size) const {
unsigned char* pbOutBytes = *_pbOut;
for (unsigned int I = 0; I < _ui32Size; ++I) {
pbOutBytes[I] = m_pbCurBufferPtr[I];
}
m_pbCurBufferPtr += _ui32Size;
}
unsigned int CIByteStream::ReadInt32() const {
unsigned int ui32Out = *reinterpret_cast<unsigned int*>(m_pbCurBufferPtr);
m_pbCurBufferPtr += sizeof(unsigned int);
return ui32Out;
}
float CIByteStream::ReadFloat() const {
float fOut = *reinterpret_cast<float*>(m_pbCurBufferPtr);
m_pbCurBufferPtr += sizeof(float);
return fOut;
}
void CIByteStream::WriteBytes(const unsigned char* _pbBytes, unsigned int _ui32BytesCount) {
::memcpy(m_pbCurBufferPtr, _pbBytes, _ui32BytesCount);
m_pbCurBufferPtr += _ui32BytesCount;
}
void CIByteStream::WriteInt32(unsigned int _ui32Value) {
::memcpy(m_pbCurBufferPtr, reinterpret_cast<unsigned char*>(&_ui32Value), sizeof(unsigned int));
m_pbCurBufferPtr += sizeof(unsigned int);
}
void CIByteStream::WriteFloat(float _fValue) {
::memcpy(m_pbCurBufferPtr, reinterpret_cast<unsigned char*>(&_fValue), sizeof(float));
m_pbCurBufferPtr += sizeof(float);
}
So, let's say you want to write your own binary model file to the secondary disk. Then you have something along these lines:
u32 ui32ByteCount = pmModel->GetByteCount();
unsigned char* pbBytes = (unsigned char*) ::malloc(ui32ByteCount);
CIByteStream ibsByteStream( pbBytes, ui32ByteCount );
pmModel->Write( ibsByteStream );
ibsByteStream.WriteBytesInFile( "Filename.bin" );
::free(pbBytes);
The ui32ByteCount variable it is the size of the model returned by CModel::GetByteCount(), which loops through every element that will be written (in the same order of reading) in ibsByteStream.WriteBytesInFile( sBuffer.c_str() ).
For your case, then you have something like:
u32 CModel::GetByteCount() {
u32 ui32Total = 0;
// We will write the array size first.
ui32Total += sizeof(u32);
// Now we write the elements.
for( u32 I = 0; I < ui32FaceCount; ++I ) {
// Assuming that there are three vertices per face.
ui32Total += 3 * sizeof(Vertex3);
}
return ui32Total;
}
void CModel::WriteToStream(CIByteStream& _ibsStream) const {
// We will write the array size first.
_ibsStream.WriteInt32( ui32FaceCount );
// Now we write the elements.
for( u32 I = 0; I < ui32FaceCount; ++I ) {
const Face& fFace = faces[I];
// Assuming that there are three vertices per face.
for (u32 J = 0; J < 3; ++J) {
for (u32 K = 0; K < 3; ++K) {
const Vertex3& vertex = fFace.vertex[J];
_ibsStream.WriteFloat( vertex[K]);
}
}
}
// When its time to convert a model:
int main(...) {
CModel mModel;
mModel.ConvertFrom("Filename.thirtypartformat");
u32 ui32ByteCount = mModel.GetByteCount();
unsigned char* pbBytes = (unsigned char*) ::malloc(ui32ByteCount);
// Note that we're keeping the file opened just after we've the data.
CIByteStream ibsStream( pbBytes, ui32ByteCount );
mModel.WriteToStream(ibsStream);
ibsStream.WriteBytesInFile("Filename.bin");
::free(pbBytes);
}
I don't know the internals of ofstream or ifstream, so I can't argue against performance very much. But the above way is much clear I think since there are no confusing overloaded operators.
Hope that helps.