diff --git a/include/cereal/archives/binary.hpp b/include/cereal/archives/binary.hpp index 4ab3b5b9..4238d019 100644 --- a/include/cereal/archives/binary.hpp +++ b/include/cereal/archives/binary.hpp @@ -31,45 +31,29 @@ #include #include -#include namespace cereal { - namespace binary_detail - { - //! Returns true if the current machine is little endian - /*! @ingroup Internal */ - inline const bool is_little_endian() - { - static std::int32_t test = 1; - return *reinterpret_cast( &test ); - } - } - // ###################################################################### //! An output archive designed to save data in a compact binary representation /*! This archive outputs data to a stream in an extremely compact binary representation with as little extra metadata as possible. + This archive does nothing to ensure that the endianness of the saved + and loaded data is the same. If you need to have portability over + architectures with different endianness, use PortableBinaryOutputArchive. + \ingroup Archives */ class BinaryOutputArchive : public OutputArchive { public: //! Construct, outputting to the provided stream /*! @param stream The stream to output to. Can be a stringstream, a file stream, or - even cout! - @param saveEndianness Whether we should output the endianness of the computer - serializing data. This only needs to be done if you - intend to send data across machines with different - endianness. If you enable this, be sure to enable it - when loading the archive. */ - BinaryOutputArchive(std::ostream & stream, bool saveEndianness = false) : + even cout! */ + BinaryOutputArchive(std::ostream & stream) : OutputArchive(this), itsStream(stream) - { - if( saveEndianness ) - this->operator()( binary_detail::is_little_endian() ); - } + { } //! Writes size bytes of data to the output stream void saveBinary( const void * data, size_t size ) @@ -86,18 +70,16 @@ namespace cereal // ###################################################################### //! An input archive designed to load data saved using BinaryOutputArchive - /*! \ingroup Archives */ + /* This archive does nothing to ensure that the endianness of the saved + and loaded data is the same. If you need to have portability over + architectures with different endianness, use PortableBinaryOutputArchive. + + \ingroup Archives */ class BinaryInputArchive : public InputArchive { public: //! Construct, loading from the provided stream - /*! @param stream The stream to read from. - @param saveEndianness Whether we should output the endianness of the computer - serializing data. This only needs to be done if you - intend to send data across machines with different - endianness. If you enable this, be sure to enable it - when loading the archive. */ - BinaryInputArchive(std::istream & stream, bool loadEndianness = false) : + BinaryInputArchive(std::istream & stream) : InputArchive(this), itsStream(stream) { } diff --git a/include/cereal/archives/portable_binary.hpp b/include/cereal/archives/portable_binary.hpp new file mode 100644 index 00000000..030ecf73 --- /dev/null +++ b/include/cereal/archives/portable_binary.hpp @@ -0,0 +1,209 @@ +/*! \file binary.hpp + \brief Binary input and output archives */ +/* + Copyright (c) 2013, Randolph Voorhies, Shane Grant + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of cereal nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef CEREAL_ARCHIVES_PORTABLE_BINARY_HPP_ +#define CEREAL_ARCHIVES_PORTABLE_BINARY_HPP_ + +#include +#include + +namespace cereal +{ + namespace portable_binary_detail + { + //! Returns true if the current machine is little endian + /*! @ingroup Internal */ + inline const bool is_little_endian() + { + static std::int32_t test = 1; + return *reinterpret_cast( &test ); + } + + //! Swaps the order of bytes for some chunk of memory + /*! @param data The data as a uint8_t pointer + @tparam DataSize The true size of the data + @ingroup Internal */ + template + inline void swap_bytes( std::uint8_t * data ) + { + for( size_t i = 0, end = DataSize / 2; i < end; ++i ) + std::swap( data[i], data[DataSize - i - 1] ); + } + } // end namespace portable_binary_detail + + // ###################################################################### + //! An output archive designed to save data in a compact binary representation + //! portable over architectures with different endianness + /*! This archive outputs data to a stream in an extremely compact binary + representation with as little extra metadata as possible. + + This archive will record the endianness of the data and assuming that + the user takes care of ensuring serialized types are the same size + across machines, is portable over different architectures. + + \ingroup Archives */ + class PortableBinaryOutputArchive : public OutputArchive + { + public: + //! Construct, outputting to the provided stream + /*! @param stream The stream to output to. Can be a stringstream, a file stream, or + even cout! */ + PortableBinaryOutputArchive(std::ostream & stream) : + OutputArchive(this), + itsStream(stream) + { + this->operator()( portable_binary_detail::is_little_endian() ); + } + + //! Writes size bytes of data to the output stream + void saveBinary( const void * data, size_t size ) + { + size_t const writtenSize = itsStream.rdbuf()->sputn( reinterpret_cast( data ), size ); + + if(writtenSize != size) + throw Exception("Failed to write " + std::to_string(size) + " bytes to output stream! Wrote " + std::to_string(writtenSize)); + } + + private: + std::ostream & itsStream; + }; + + // ###################################################################### + //! An input archive designed to load data saved using PortableBinaryOutputArchive + /*! This archive outputs data to a stream in an extremely compact binary + representation with as little extra metadata as possible. + + This archive will load the endianness of the serialized data and + if necessary transform it to match that of the local machine. This comes + at a significant performance cost compared to non portable archives if + the transformation is necessary, and also causes a small performance hit + even if it is not necessary. + + It is recommended to use portable archives only if you know that you will + be sending binary data to machines with different endianness. + + The archive will do nothing to ensure types are the same size - that is + the responsibility of the user. + + \ingroup Archives */ + class PortableBinaryInputArchive : public InputArchive + { + public: + //! Construct, loading from the provided stream + /*! @param stream The stream to read from. */ + PortableBinaryInputArchive(std::istream & stream) : + InputArchive(this), + itsStream(stream) + { + bool streamLittleEndian; + this->operator()( streamLittleEndian ); + itsConvertEndianness = portable_binary_detail::is_little_endian() ^ streamLittleEndian; + } + + //! Reads size bytes of data from the input stream + /*! @param data The data to save + @param size The number of bytes in the data + @tparam DataSize T The size of the actual type of the data elements being loaded */ + template + void loadBinary( void * const data, size_t size ) + { + // load data + size_t const readSize = itsStream.rdbuf()->sgetn( reinterpret_cast( data ), size ); + + if(readSize != size) + throw Exception("Failed to read " + std::to_string(size) + " bytes from input stream! Read " + std::to_string(readSize)); + + // flip bits if needed + if( itsConvertEndianness ) + { + std::uint8_t * ptr = reinterpret_cast( data ); + for( size_t i = 0; i < size; i += DataSize ) + portable_binary_detail::swap_bytes( ptr ); + } + } + + private: + std::istream & itsStream; + bool itsConvertEndianness; //!< If set to true, we will need to swap bytes upon loading + }; + + // ###################################################################### + // Common BinaryArchive serialization functions + + //! Saving for POD types to portable binary + template inline + typename std::enable_if::value, void>::type + save(PortableBinaryOutputArchive & ar, T const & t) + { + ar.saveBinary(std::addressof(t), sizeof(t)); + } + + //! Loading for POD types from portable binary + template inline + typename std::enable_if::value, void>::type + load(PortableBinaryInputArchive & ar, T & t) + { + ar.template loadBinary(std::addressof(t), sizeof(t)); + } + + //! Serializing NVP types to portable binary + template inline + CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive) + serialize( Archive & ar, NameValuePair & t ) + { + ar( t.value ); + } + + //! Serializing SizeTags to portable binary + template inline + CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive) + serialize( Archive & ar, SizeTag & t ) + { + ar( t.size ); + } + + //! Saving binary data to portable binary + template inline + void save(PortableBinaryOutputArchive & ar, BinaryData const & bd) + { + ar.saveBinary(bd.data, bd.size); + } + + //! Loading binary data from portable binary + template inline + void load(PortableBinaryInputArchive & ar, BinaryData & bd) + { + ar.template loadBinary::type)>(bd.data, bd.size); + } +} // namespace cereal + +// register archives for polymorphic support +CEREAL_REGISTER_ARCHIVE(cereal::PortableBinaryOutputArchive); +CEREAL_REGISTER_ARCHIVE(cereal::PortableBinaryInputArchive); + +#endif // CEREAL_ARCHIVES_PORTABLE_BINARY_HPP_ diff --git a/sandbox.cpp b/sandbox.cpp index fa0d2f0f..2a4ff665 100644 --- a/sandbox.cpp +++ b/sandbox.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -464,8 +465,37 @@ int main() } { - std::ofstream b("test.out"); - cereal::BinaryOutputArchive oar(b, true); + std::ofstream b("endian.out"); + cereal::PortableBinaryOutputArchive oar(b); + + bool bb = true; + char a = 'a'; + int x = 1234; + float y = 1.324f; + double z = 3.1452; + long double d = 1.123451234512345; + long long j = 2394873298472343; + + oar( bb, a, x, y, z, d, j ); + std::cout << bb << " " << a << " " << x << " " << y << " " << z << " " << d << " " << j << std::endl; + } + { + std::ifstream b("endian.out"); + cereal::PortableBinaryInputArchive iar(b); + + bool bb; + char a; + int x; + float y; + double z; + long double d; + long long j; + + iar( bb, a, x, y, z, d, j ); + + std::cout << bb << " " << a << " " << x << " " << y << " " << z << " " << d << " " << j << std::endl; + + std::remove("endian.out"); }