// Copyright (C) 2006 Douglas Gregor <doug.gregor -at- gmail.com> | |
// Use, modification and distribution is subject to the Boost Software | |
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at | |
// http://www.boost.org/LICENSE_1_0.txt) | |
// Authors: Douglas Gregor | |
/** @file serialize.hpp | |
* | |
* This file provides Boost.Serialization support for Python objects | |
* within Boost.MPI. Python objects can be serialized in one of two | |
* ways. The default serialization method involves using the Python | |
* "pickle" module to pickle the Python objects, transmits the | |
* pickled representation, and unpickles the result when | |
* received. For C++ types that have been exposed to Python and | |
* registered with register_serialized(), objects are directly | |
* serialized for transmissing, skipping the pickling step. | |
*/ | |
#ifndef BOOST_MPI_PYTHON_SERIALIZE_HPP | |
#define BOOST_MPI_PYTHON_SERIALIZE_HPP | |
#include <boost/mpi/python/config.hpp> | |
#include <boost/python/object.hpp> | |
#include <boost/python/str.hpp> | |
#include <boost/python/extract.hpp> | |
#include <memory> | |
#include <map> | |
#include <boost/function/function3.hpp> | |
#include <boost/mpl/bool.hpp> | |
#include <boost/mpl/if.hpp> | |
#include <boost/serialization/split_free.hpp> | |
#include <boost/serialization/array.hpp> | |
#include <boost/assert.hpp> | |
#include <boost/type_traits/is_fundamental.hpp> | |
#define BOOST_MPI_PYTHON_FORWARD_ONLY | |
#include <boost/mpi/python.hpp> | |
/************************************************************************ | |
* Boost.Python Serialization Section * | |
************************************************************************/ | |
#if !defined(BOOST_NO_SFINAE) && !defined(BOOST_NO_IS_CONVERTIBLE) | |
/** | |
* @brief Declare IArchive and OArchive as a Boost.Serialization | |
* archives that can be used for Python objects. | |
* | |
* This macro can only be expanded from the global namespace. It only | |
* requires that Archiver be forward-declared. IArchiver and OArchiver | |
* will only support Serialization of Python objects by pickling | |
* them. If the Archiver type should also support "direct" | |
* serialization (for C++ types), use | |
* BOOST_PYTHON_DIRECT_SERIALIZATION_ARCHIVE instead. | |
*/ | |
# define BOOST_PYTHON_SERIALIZATION_ARCHIVE(IArchiver, OArchiver) \ | |
namespace boost { namespace python { namespace api { \ | |
template<typename R, typename T> \ | |
struct enable_binary< IArchiver , R, T> {}; \ | |
\ | |
template<typename R, typename T> \ | |
struct enable_binary< OArchiver , R, T> {}; \ | |
} } } | |
# else | |
# define BOOST_PYTHON_SERIALIZATION_ARCHIVE(IArchiver, OArchiver) | |
#endif | |
/** | |
* @brief Declare IArchiver and OArchiver as a Boost.Serialization | |
* archives that can be used for Python objects and C++ objects | |
* wrapped in Python. | |
* | |
* This macro can only be expanded from the global namespace. It only | |
* requires that IArchiver and OArchiver be forward-declared. However, | |
* note that you will also need to write | |
* BOOST_PYTHON_DIRECT_SERIALIZATION_ARCHIVE_IMPL(IArchiver, | |
* OArchiver) in one of your translation units. | |
DPG PICK UP HERE | |
*/ | |
#define BOOST_PYTHON_DIRECT_SERIALIZATION_ARCHIVE(IArchiver, OArchiver) \ | |
BOOST_PYTHON_SERIALIZATION_ARCHIVE(IArchiver, OArchiver) \ | |
namespace boost { namespace python { namespace detail { \ | |
template<> \ | |
BOOST_MPI_PYTHON_DECL direct_serialization_table< IArchiver , OArchiver >& \ | |
get_direct_serialization_table< IArchiver , OArchiver >(); \ | |
} \ | |
\ | |
template<> \ | |
struct has_direct_serialization< IArchiver , OArchiver> : mpl::true_ { }; \ | |
\ | |
template<> \ | |
struct output_archiver< IArchiver > { typedef OArchiver type; }; \ | |
\ | |
template<> \ | |
struct input_archiver< OArchiver > { typedef IArchiver type; }; \ | |
} } | |
/** | |
* @brief Define the implementation for Boost.Serialization archivers | |
* that can be used for Python objects and C++ objects wrapped in | |
* Python. | |
* | |
* This macro can only be expanded from the global namespace. It only | |
* requires that IArchiver and OArchiver be forward-declared. Before | |
* using this macro, you will need to declare IArchiver and OArchiver | |
* as direct serialization archives with | |
* BOOST_PYTHON_DIRECT_SERIALIZATION_ARCHIVE(IArchiver, OArchiver). | |
*/ | |
#define BOOST_PYTHON_DIRECT_SERIALIZATION_ARCHIVE_IMPL(IArchiver, OArchiver) \ | |
namespace boost { namespace python { namespace detail { \ | |
template \ | |
class BOOST_MPI_PYTHON_DECL direct_serialization_table< IArchiver , OArchiver >; \ | |
\ | |
template<> \ | |
BOOST_MPI_PYTHON_DECL \ | |
direct_serialization_table< IArchiver , OArchiver >& \ | |
get_direct_serialization_table< IArchiver , OArchiver >( ) \ | |
{ \ | |
static direct_serialization_table< IArchiver, OArchiver > table; \ | |
return table; \ | |
} \ | |
} } } | |
namespace boost { namespace python { | |
/** | |
* INTERNAL ONLY | |
* | |
* Provides access to the Python "pickle" module from within C++. | |
*/ | |
class BOOST_MPI_PYTHON_DECL pickle { | |
struct data_t; | |
public: | |
static str dumps(object obj, int protocol = -1); | |
static object loads(str s); | |
private: | |
static void initialize_data(); | |
static data_t* data; | |
}; | |
/** | |
* @brief Whether the input/output archiver pair has "direct" | |
* serialization for C++ objects exposed in Python. | |
* | |
* Users do not typically need to specialize this trait, as it will be | |
* specialized as part of the macro | |
* BOOST_PYTHON_DIRECT_SERIALIZATION_ARCHIVE. | |
*/ | |
template<typename IArchiver, typename OArchiver> | |
struct has_direct_serialization : mpl::false_ { }; | |
/** | |
* @brief A metafunction that determines the output archiver for the | |
* given input archiver. | |
* | |
* Users do not typically need to specialize this trait, as it will be | |
* specialized as part of the macro | |
* BOOST_PYTHON_DIRECT_SERIALIZATION_ARCHIVE. | |
*/ | |
template<typename IArchiver> struct output_archiver { }; | |
/** | |
* @brief A metafunction that determines the input archiver for the | |
* given output archiver. | |
* | |
* Users do not typically need to specialize this trait, as it will be | |
* specialized as part of the macro | |
* BOOST_PYTHON_DIRECT_SERIALIZATION_ARCHIVE. | |
* | |
*/ | |
template<typename OArchiver> struct input_archiver { }; | |
namespace detail { | |
/** | |
* INTERNAL ONLY | |
* | |
* This class contains the direct-serialization code for the given | |
* IArchiver/OArchiver pair. It is intended to be used as a | |
* singleton class, and will be accessed when (de-)serializing a | |
* Boost.Python object with an archiver that supports direct | |
* serializations. Do not create instances of this class directly: | |
* instead, use get_direct_serialization_table. | |
*/ | |
template<typename IArchiver, typename OArchiver> | |
class BOOST_MPI_PYTHON_DECL direct_serialization_table | |
{ | |
public: | |
typedef boost::function3<void, OArchiver&, const object&, const unsigned int> | |
saver_t; | |
typedef boost::function3<void, IArchiver&, object&, const unsigned int> | |
loader_t; | |
typedef std::map<PyTypeObject*, std::pair<int, saver_t> > savers_t; | |
typedef std::map<int, loader_t> loaders_t; | |
/** | |
* Retrieve the saver (serializer) associated with the Python | |
* object @p obj. | |
* | |
* @param obj The object we want to save. Only its (Python) type | |
* is important. | |
* | |
* @param descriptor The value of the descriptor associated to | |
* the returned saver. Will be set to zero if no saver was found | |
* for @p obj. | |
* | |
* @returns a function object that can be used to serialize this | |
* object (and other objects of the same type), if possible. If | |
* no saver can be found, returns an empty function object.. | |
*/ | |
saver_t saver(const object& obj, int& descriptor) | |
{ | |
typename savers_t::iterator pos = savers.find(obj.ptr()->ob_type); | |
if (pos != savers.end()) { | |
descriptor = pos->second.first; | |
return pos->second.second; | |
} | |
else { | |
descriptor = 0; | |
return saver_t(); | |
} | |
} | |
/** | |
* Retrieve the loader (deserializer) associated with the given | |
* descriptor. | |
* | |
* @param descriptor The descriptor number provided by saver() | |
* when determining the saver for this type. | |
* | |
* @returns a function object that can be used to deserialize an | |
* object whose type is the same as that corresponding to the | |
* descriptor. If the descriptor is unknown, the return value | |
* will be an empty function object. | |
*/ | |
loader_t loader(int descriptor) | |
{ | |
typename loaders_t::iterator pos = loaders.find(descriptor); | |
if (pos != loaders.end()) | |
return pos->second; | |
else | |
return loader_t(); | |
} | |
/** | |
* Register the type T for direct serialization. | |
* | |
* @param value A sample value of the type @c T. This may be used | |
* to compute the Python type associated with the C++ type @c T. | |
* | |
* @param type The Python type associated with the C++ type @c | |
* T. If not provided, it will be computed from the same value @p | |
* value. | |
*/ | |
template<typename T> | |
void register_type(const T& value = T(), PyTypeObject* type = 0) | |
{ | |
// If the user did not provide us with a Python type, figure it | |
// out for ourselves. | |
if (!type) { | |
object obj(value); | |
type = obj.ptr()->ob_type; | |
} | |
register_type(default_saver<T>(), default_loader<T>(type), value, type); | |
} | |
/** | |
* Register the type T for direct serialization. | |
* | |
* @param saver A function object that will serialize a | |
* Boost.Python object (that represents a C++ object of type @c | |
* T) to an @c OArchive. | |
* | |
* @param loader A function object that will deserialize from an | |
* @c IArchive into a Boost.Python object that represents a C++ | |
* object of type @c T. | |
* | |
* @param value A sample value of the type @c T. This may be used | |
* to compute the Python type associated with the C++ type @c T. | |
* | |
* @param type The Python type associated with the C++ type @c | |
* T. If not provided, it will be computed from the same value @p | |
* value. | |
*/ | |
template<typename T> | |
void register_type(const saver_t& saver, const loader_t& loader, | |
const T& value = T(), PyTypeObject* type = 0) | |
{ | |
// If the user did not provide us with a Python type, figure it | |
// out for ourselves. | |
if (!type) { | |
object obj(value); | |
type = obj.ptr()->ob_type; | |
} | |
int descriptor = savers.size() + 1; | |
if (savers.find(type) != savers.end()) | |
return; | |
savers[type] = std::make_pair(descriptor, saver); | |
loaders[descriptor] = loader; | |
} | |
protected: | |
template<typename T> | |
struct default_saver { | |
void operator()(OArchiver& ar, const object& obj, const unsigned int) { | |
T value = extract<T>(obj)(); | |
ar << value; | |
} | |
}; | |
template<typename T> | |
struct default_loader { | |
default_loader(PyTypeObject* type) : type(type) { } | |
void operator()(IArchiver& ar, object& obj, const unsigned int) { | |
// If we can, extract the object in place. | |
if (!is_fundamental<T>::value && obj && obj.ptr()->ob_type == type) { | |
ar >> extract<T&>(obj)(); | |
} else { | |
T value; | |
ar >> value; | |
obj = object(value); | |
} | |
} | |
private: | |
PyTypeObject* type; | |
}; | |
savers_t savers; | |
loaders_t loaders; | |
}; | |
/** | |
* @brief Retrieve the direct-serialization table for an | |
* IArchiver/OArchiver pair. | |
* | |
* This function is responsible for returning a reference to the | |
* singleton direct-serialization table. Its primary template is | |
* left undefined, to force the use of an explicit specialization | |
* with a definition in a single translation unit. Use the macro | |
* BOOST_PYTHON_DIRECT_SERIALIZATION_ARCHIVE_IMPL to define this | |
* explicit specialization. | |
*/ | |
template<typename IArchiver, typename OArchiver> | |
direct_serialization_table<IArchiver, OArchiver>& | |
get_direct_serialization_table(); | |
} // end namespace detail | |
/** | |
* @brief Register the type T for direct serialization. | |
* | |
* The @c register_serialized function registers a C++ type for direct | |
* serialization with the given @c IArchiver/@c OArchiver pair. Direct | |
* serialization elides the use of the Python @c pickle package when | |
* serializing Python objects that represent C++ values. Direct | |
* serialization can be beneficial both to improve serialization | |
* performance (Python pickling can be very inefficient) and to permit | |
* serialization for Python-wrapped C++ objects that do not support | |
* pickling. | |
* | |
* @param value A sample value of the type @c T. This may be used | |
* to compute the Python type associated with the C++ type @c T. | |
* | |
* @param type The Python type associated with the C++ type @c | |
* T. If not provided, it will be computed from the same value @p | |
* value. | |
*/ | |
template<typename IArchiver, typename OArchiver, typename T> | |
void | |
register_serialized(const T& value = T(), PyTypeObject* type = 0) | |
{ | |
detail::direct_serialization_table<IArchiver, OArchiver>& table = | |
detail::get_direct_serialization_table<IArchiver, OArchiver>(); | |
table.register_type(value, type); | |
} | |
namespace detail { | |
/// Save a Python object by pickling it. | |
template<typename Archiver> | |
void | |
save_impl(Archiver& ar, const boost::python::object& obj, | |
const unsigned int /*version*/, | |
mpl::false_ /*has_direct_serialization*/) | |
{ | |
boost::python::str py_string = boost::python::pickle::dumps(obj); | |
int len = boost::python::extract<int>(py_string.attr("__len__")()); | |
const char* string = boost::python::extract<const char*>(py_string); | |
ar << len << boost::serialization::make_array(string, len); | |
} | |
/// Try to save a Python object by directly serializing it; fall back | |
/// on pickling if required. | |
template<typename Archiver> | |
void | |
save_impl(Archiver& ar, const boost::python::object& obj, | |
const unsigned int version, | |
mpl::true_ /*has_direct_serialization*/) | |
{ | |
typedef Archiver OArchiver; | |
typedef typename input_archiver<OArchiver>::type IArchiver; | |
typedef typename direct_serialization_table<IArchiver, OArchiver>::saver_t | |
saver_t; | |
direct_serialization_table<IArchiver, OArchiver>& table = | |
get_direct_serialization_table<IArchiver, OArchiver>(); | |
int descriptor = 0; | |
if (saver_t saver = table.saver(obj, descriptor)) { | |
ar << descriptor; | |
saver(ar, obj, version); | |
} else { | |
// Pickle it | |
ar << descriptor; | |
detail::save_impl(ar, obj, version, mpl::false_()); | |
} | |
} | |
/// Load a Python object by unpickling it | |
template<typename Archiver> | |
void | |
load_impl(Archiver& ar, boost::python::object& obj, | |
const unsigned int /*version*/, | |
mpl::false_ /*has_direct_serialization*/) | |
{ | |
int len; | |
ar >> len; | |
std::auto_ptr<char> string(new char[len]); | |
ar >> boost::serialization::make_array(string.get(), len); | |
boost::python::str py_string(string.get(), len); | |
obj = boost::python::pickle::loads(py_string); | |
} | |
/// Try to load a Python object by directly deserializing it; fall back | |
/// on unpickling if required. | |
template<typename Archiver> | |
void | |
load_impl(Archiver& ar, boost::python::object& obj, | |
const unsigned int version, | |
mpl::true_ /*has_direct_serialization*/) | |
{ | |
typedef Archiver IArchiver; | |
typedef typename output_archiver<IArchiver>::type OArchiver; | |
typedef typename direct_serialization_table<IArchiver, OArchiver>::loader_t | |
loader_t; | |
direct_serialization_table<IArchiver, OArchiver>& table = | |
get_direct_serialization_table<IArchiver, OArchiver>(); | |
int descriptor; | |
ar >> descriptor; | |
if (descriptor) { | |
loader_t loader = table.loader(descriptor); | |
BOOST_ASSERT(loader); | |
loader(ar, obj, version); | |
} else { | |
// Unpickle it | |
detail::load_impl(ar, obj, version, mpl::false_()); | |
} | |
} | |
} // end namespace detail | |
template<typename Archiver> | |
void | |
save(Archiver& ar, const boost::python::object& obj, | |
const unsigned int version) | |
{ | |
typedef Archiver OArchiver; | |
typedef typename input_archiver<OArchiver>::type IArchiver; | |
detail::save_impl(ar, obj, version, | |
has_direct_serialization<IArchiver, OArchiver>()); | |
} | |
template<typename Archiver> | |
void | |
load(Archiver& ar, boost::python::object& obj, | |
const unsigned int version) | |
{ | |
typedef Archiver IArchiver; | |
typedef typename output_archiver<IArchiver>::type OArchiver; | |
detail::load_impl(ar, obj, version, | |
has_direct_serialization<IArchiver, OArchiver>()); | |
} | |
template<typename Archive> | |
inline void | |
serialize(Archive& ar, boost::python::object& obj, const unsigned int version) | |
{ | |
boost::serialization::split_free(ar, obj, version); | |
} | |
} } // end namespace boost::python | |
/************************************************************************ | |
* Boost.MPI-Specific Section * | |
************************************************************************/ | |
namespace boost { namespace mpi { | |
class packed_iarchive; | |
class packed_oarchive; | |
} } // end namespace boost::mpi | |
BOOST_PYTHON_DIRECT_SERIALIZATION_ARCHIVE( | |
::boost::mpi::packed_iarchive, | |
::boost::mpi::packed_oarchive) | |
namespace boost { namespace mpi { namespace python { | |
template<typename T> | |
void | |
register_serialized(const T& value, PyTypeObject* type) | |
{ | |
using boost::python::register_serialized; | |
register_serialized<packed_iarchive, packed_oarchive>(value, type); | |
} | |
} } } // end namespace boost::mpi::python | |
#endif // BOOST_MPI_PYTHON_SERIALIZE_HPP |