diff --git a/build/gcc.mak b/build/gcc.mak index d0b42548..f71185e0 100644 --- a/build/gcc.mak +++ b/build/gcc.mak @@ -1,3 +1,10 @@ +# Revision History + +# 04 Mar 01 Changed library name to libboost_python.a, various cleanups, +# attempted Cygwin compatibility. Still needs testing on Linux +# (David Abrahams) + + LIBSRC = \ classes.cpp \ conversions.cpp \ @@ -11,13 +18,16 @@ LIBSRC = \ LIBOBJ = $(LIBSRC:.cpp=.o) OBJ = $(LIBOBJ) +PYTHON_INC=$(ROOT)/usr/local/include/python2.0 +# libpython2.0.dll ifeq "$(OS)" "Windows_NT" -PYTHON_LIB=c:/tools/python/libs/python15.lib -INC = -Ic:/cygnus/usr/include/g++-3 -Ic:/cygnus/usr/include -Ic:/boost -Ic:/tools/python/include +ROOT=c:/cygnus +INC = -Ic:/cygnus/usr/include/g++-3 -Ic:/cygnus/usr/include -Ic:/boost -I$(PYTHON_INC) MODULE_EXTENSION=dll +PYTHON_LIB=c:/cygnus/usr/local/lib/python2.0/config/libpython2.0.dll.a else -INC = -I/usr/local/include/python1.5 +INC = -I$(PYTHON_INC) MODULE_EXTENSION=so endif @@ -31,20 +41,30 @@ endif [ -s $@ ] || rm -f $@ -example1: example1.o libpycpp.a - g++ -shared -o ../example/hellomodule.$(MODULE_EXTENSION) $(PYHTON_LIB) example1.o -L. -lpycpp - python ../example/test_example1.py +PYTHON = python + +test: comprehensive.o libboost_python.a + g++ $(CXXFLAGS) -shared -o ../test/boost_python_test.$(MODULE_EXTENSION) comprehensive.o -L. -lboost_python $(PYTHON_LIB) + $(PYTHON) ../test/comprehensive.py + +comprehensive.o: ../test/comprehensive.cpp + g++ $(CXXFLAGS) --template-depth-32 -fPIC -Wall -W $(INC) -o $*.o -c $< + + +example1: example1.o libboost_python.a + g++ $(CXXFLAGS) -shared -o ../example/hellomodule.$(MODULE_EXTENSION) example1.o -L. -lboost_python $(PYTHON_LIB) + $(PYTHON) ../example/test_example1.py example1.o: ../example/example1.cpp - g++ -fPIC -Wall -W $(INC) -o $*.o -c $< + g++ $(CXXFLAGS) --template-depth-32 -fPIC -Wall -W $(INC) -o $*.o -c $< clean: rm -rf *.o *.$(MODULE_EXTENSION) *.a *.d *.pyc *.bak a.out -libpycpp.a: $(LIBOBJ) - rm -f libpycpp.a - ar cq libpycpp.a $(LIBOBJ) +libboost_python.a: $(LIBOBJ) + rm -f libboost_python.a + ar cq libboost_python.a $(LIBOBJ) DEP = $(OBJ:.o=.d) diff --git a/src/classes.cpp b/src/classes.cpp index 0c2aa6e5..c609eeef 100644 --- a/src/classes.cpp +++ b/src/classes.cpp @@ -5,6 +5,11 @@ // // The author gratefully acknowleges the support of Dragon Systems, Inc., in // producing this work. +// +// Revision History: +// 04 Mar 01 Rolled in const_cast from Dragon fork (Dave Abrahams) +// 03 Mar 01 added: pickle safety measures (Ralf W. Grosse-Kunstleve) +// 03 Mar 01 bug fix: use bound_function::create() (instead of new bound_function) #include #include @@ -877,7 +882,7 @@ namespace { PyObject *globals = PyEval_GetGlobals(); if (globals != NULL) { - PyObject *module_name = PyDict_GetItemString(globals, "__name__"); + PyObject *module_name = PyDict_GetItemString(globals, const_cast("__name__")); if (module_name != NULL) name_space.set_item(module_key, module_name); } diff --git a/src/extension_class.cpp b/src/extension_class.cpp index c15e538b..f71976b9 100644 --- a/src/extension_class.cpp +++ b/src/extension_class.cpp @@ -5,6 +5,9 @@ // // The author gratefully acknowleges the support of Dragon Systems, Inc., in // producing this work. +// +// Revision History: +// 04 Mar 01 Use PyObject_INIT() instead of trying to hand-initialize (David Abrahams) #include #include @@ -446,8 +449,8 @@ operator_dispatcher::operator_dispatcher(const ref& o, const ref& s) : m_object(o), m_self(s), m_free_list_link(0) { - ob_refcnt = 1; - ob_type = &type_obj; + PyObject* self = this; + PyObject_INIT(self, &type_obj); } operator_dispatcher* @@ -460,7 +463,9 @@ operator_dispatcher::create(const ref& object, const ref& self) free_list = result->m_free_list_link; result->m_object = object; result->m_self = self; - Py_INCREF(result); + + PyObject* result_as_pyobject = result; + PyObject_INIT(result_as_pyobject, &type_obj); return result; } diff --git a/src/gen_extclass.py b/src/gen_extclass.py index 7c378d45..2d261289 100644 --- a/src/gen_extclass.py +++ b/src/gen_extclass.py @@ -27,6 +27,7 @@ def gen_extclass(args): # include # include # include +# include namespace boost { namespace python { @@ -138,6 +139,26 @@ class class_registry static std::vector static_derived_class_info; }; +template +struct is_null_helper +{ + template + static bool test(Ptr x) { return x == 0; } +}; + +template <> +struct is_null_helper +{ + template + static bool test(const Ptr& x) { return x.get() == 0; } +}; + +template +bool is_null(const Ptr& x) +{ + return is_null_helper<(is_pointer::value)>::test(x); +}; + }}} // namespace boost::python::detail BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE @@ -183,9 +204,9 @@ class python_extension_class_converters new boost::python::detail::instance_value_holder(result.get(), x))); return result.release(); } - - // Convert to T* - friend T* from_python(PyObject* obj, boost::python::type) + + friend + T* non_null_from_python(PyObject* obj, boost::python::type) { // downcast to an extension_instance, then find the actual T boost::python::detail::extension_instance* self = boost::python::detail::get_extension_instance(obj); @@ -206,9 +227,18 @@ class python_extension_class_converters throw boost::python::argument_error(); } - // Convert to PtrType, where PtrType can be dereferenced to obtain a T. + // Convert to T* + friend T* from_python(PyObject* obj, boost::python::type) + { + if (obj == Py_None) + return 0; + else + return non_null_from_python(obj, boost::python::type()); + } + + // Extract from obj a mutable reference to the PtrType object which is holding a T. template - static PtrType& ptr_from_python(PyObject* obj, boost::python::type) + static PtrType& smart_ptr_reference(PyObject* obj, boost::python::type) { // downcast to an extension_instance, then find the actual T boost::python::detail::extension_instance* self = boost::python::detail::get_extension_instance(obj); @@ -225,9 +255,27 @@ class python_extension_class_converters throw boost::python::argument_error(); } + // Extract from obj a constant reference to the PtrType object which is holding a T. + // If obj is None, the reference denotes a default-constructed PtrType template - static PyObject* ptr_to_python(PtrType x) + static const PtrType& smart_ptr_value(PyObject* obj, boost::python::type) { + if (obj == Py_None) + { + static PtrType null_ptr; + return null_ptr; + } + return smart_ptr_reference(obj, boost::python::type()); + } + + template + static PyObject* smart_ptr_to_python(PtrType x) + { + if (boost::python::detail::is_null(x)) + { + return boost::python::detail::none(); + } + boost::python::reference result(create_instance()); result->add_implementation( std::auto_ptr( @@ -259,7 +307,7 @@ class python_extension_class_converters // Convert to T& friend T& from_python(PyObject* p, boost::python::type) - { return *boost::python::detail::check_non_null(from_python(p, boost::python::type())); } + { return *boost::python::detail::check_non_null(non_null_from_python(p, boost::python::type())); } // Convert to const T& friend const T& from_python(PyObject* p, boost::python::type) @@ -270,28 +318,28 @@ class python_extension_class_converters { return from_python(p, boost::python::type()); } friend std::auto_ptr& from_python(PyObject* p, boost::python::type&>) - { return ptr_from_python(p, boost::python::type >()); } + { return smart_ptr_reference(p, boost::python::type >()); } - friend std::auto_ptr& from_python(PyObject* p, boost::python::type >) - { return ptr_from_python(p, boost::python::type >()); } + friend const std::auto_ptr& from_python(PyObject* p, boost::python::type >) + { return smart_ptr_value(p, boost::python::type >()); } friend const std::auto_ptr& from_python(PyObject* p, boost::python::type&>) - { return ptr_from_python(p, boost::python::type >()); } + { return smart_ptr_value(p, boost::python::type >()); } friend PyObject* to_python(std::auto_ptr x) - { return ptr_to_python(x); } + { return smart_ptr_to_python(x); } friend boost::shared_ptr& from_python(PyObject* p, boost::python::type&>) - { return ptr_from_python(p, boost::python::type >()); } + { return smart_ptr_reference(p, boost::python::type >()); } - friend boost::shared_ptr& from_python(PyObject* p, boost::python::type >) - { return ptr_from_python(p, boost::python::type >()); } + friend const boost::shared_ptr& from_python(PyObject* p, boost::python::type >) + { return smart_ptr_value(p, boost::python::type >()); } friend const boost::shared_ptr& from_python(PyObject* p, boost::python::type&>) - { return ptr_from_python(p, boost::python::type >()); } + { return smart_ptr_value(p, boost::python::type >()); } friend PyObject* to_python(boost::shared_ptr x) - { return ptr_to_python(x); } + { return smart_ptr_to_python(x); } }; // Convert T to_python, instantiated on demand and only if there isn't a diff --git a/test/comprehensive.cpp b/test/comprehensive.cpp index 7fad74bb..3d173310 100644 --- a/test/comprehensive.cpp +++ b/test/comprehensive.cpp @@ -5,6 +5,10 @@ // // The author gratefully acknowleges the support of Dragon Systems, Inc., in // producing this work. + +// Revision History: +// 04 Mar 01 Changed name of extension module so it would work with DebugPython, +// eliminated useless test that aggravated MSVC (David Abrahams) #include "comprehensive.hpp" #include #include // used for portability on broken compilers @@ -238,6 +242,23 @@ boost::shared_ptr Baz::create_foo() return boost::shared_ptr(new DerivedFromFoo(0)); } +// Used to check conversion to None +boost::shared_ptr foo_factory(bool create) +{ + return boost::shared_ptr(create ? new DerivedFromFoo(0) : 0); +} + +// Used to check conversion from None +bool foo_ptr_is_null(Foo* p) +{ + return p == 0; +} + +bool foo_shared_ptr_is_null(boost::shared_ptr p) +{ + return p.get() == 0; +} + // We can accept smart pointer parameters int Baz::get_foo_value(boost::shared_ptr foo) { @@ -404,7 +425,7 @@ static int testUpcast(Base* b) static std::auto_ptr derived1Factory(int i) { - return std::auto_ptr(new Derived1(i)); + return std::auto_ptr(i < 0 ? 0 : new Derived1(i)); } static std::auto_ptr derived2Factory(int i) @@ -815,6 +836,32 @@ namespace bpl_test { w.set_secret_number(number); } + // Test plain char converters. + char get_plain_char() { return 'x'; } + std::string use_plain_char(char c) { return std::string(3, c); } + + // This doesn't test anything but the compiler, since it has the same signature as the above. + // Since MSVC is broken and gets the signature wrong, we'll skip it. + std::string use_const_plain_char( +#ifndef BOOST_MSVC6_OR_EARLIER + const +#endif + char c) { return std::string(5, c); } + + // Test std::complex converters. + std::complex dpolar(double rho, double theta) { + return std::polar(rho, theta); + } + double dreal(const std::complex& c) { return c.real(); } + double dimag(std::complex c) { return c.imag(); } + + // Test std::complex converters. + std::complex fpolar(float rho, float theta) { + return std::polar(rho, theta); + } + double freal(const std::complex& c) { return c.real(); } + double fimag(std::complex c) { return c.imag(); } + /************************************************************/ /* */ /* init the module */ @@ -1036,6 +1083,26 @@ void init_module(boost::python::module_builder& m) world_class.def(world_getinitargs, "__getinitargs__"); world_class.def(world_getstate, "__getstate__"); world_class.def(world_setstate, "__setstate__"); + + // Test plain char converters. + m.def(get_plain_char, "get_plain_char"); + m.def(use_plain_char, "use_plain_char"); + m.def(use_const_plain_char, "use_const_plain_char"); + + // Test std::complex converters. + m.def(dpolar, "dpolar"); + m.def(dreal, "dreal"); + m.def(dimag, "dimag"); + + // Test std::complex converters. + m.def(fpolar, "fpolar"); + m.def(freal, "freal"); + m.def(fimag, "fimag"); + + // Test new null-pointer<->None conversions + m.def(foo_factory, "foo_factory"); + m.def(foo_ptr_is_null, "foo_ptr_is_null"); + m.def(foo_shared_ptr_is_null, "foo_shared_ptr_is_null"); } PyObject* raw(const boost::python::tuple& args, const boost::python::dictionary& keywords) @@ -1057,18 +1124,18 @@ PyObject* raw(const boost::python::tuple& args, const boost::python::dictionary& void init_module() { - boost::python::module_builder test("test"); - init_module(test); + boost::python::module_builder boost_python_test("boost_python_test"); + init_module(boost_python_test); // Just for giggles, add a raw metaclass. - test.add(new boost::python::meta_class); + boost_python_test.add(new boost::python::meta_class); } extern "C" #ifdef _WIN32 __declspec(dllexport) #endif -void inittest() +void initboost_python_test() { try { bpl_test::init_module(); diff --git a/test/comprehensive.py b/test/comprehensive.py index a8e181d2..c1424d25 100644 --- a/test/comprehensive.py +++ b/test/comprehensive.py @@ -7,6 +7,15 @@ r''' // The author gratefully acknowleges the support of Dragon Systems, Inc., in // producing this work. +// Revision History: +// 04 Mar 01 Changed name of extension module so it would work with DebugPython, +// fixed exception message checking to work with Python 2.0 +// (Dave Abrahams) + +Load up the extension module + + >>> from boost_python_test import * + Automatic checking of the number and type of arguments. Foo's constructor takes a single long parameter. @@ -17,9 +26,9 @@ a single long parameter. >>> try: ext = Foo('foo') ... except TypeError, err: - ... assert re.match( - ... '(illegal argument type for built-in operation)|(an integer is required)', str(err)) - ... else: print 'no exception' + ... assert_integer_expected(err) + ... else: + ... print 'no exception' >>> ext = Foo(1) @@ -64,6 +73,21 @@ We can subclass Foo. >>> b.call_pure() 'not pure anymore!' +None corresponds to a NULL pointer or smart pointer + >>> f = foo_factory(1) + >>> f.add_len('xxx') + 1000 + >>> foo_factory(0) is None + 1 + >>> foo_ptr_is_null(None) + 1 + >>> foo_ptr_is_null(f) + 0 + >>> foo_shared_ptr_is_null(None) + 1 + >>> foo_shared_ptr_is_null(f) + 0 + If no __init__ function is defined, the one from the base class takes effect, just like in a Python class. @@ -209,7 +233,7 @@ Polymorphism also works: Pickling tests: >>> world.__module__ - 'test' + 'boost_python_test' >>> world.__safe_for_unpickling__ 1 >>> world.__reduce__() @@ -239,6 +263,47 @@ Pickling tests: Hello from California! 42 Hello from California! 0 +Pickle safety measures: + >>> r=Rational(3, 4) + >>> r + Rational(3, 4) + >>> try: s=pickle.dumps(r) + ... except RuntimeError, err: print err[0] + ... + Incomplete pickle support (__dict_defines_state__ not set) + >>> class myrational(Rational): + ... __dict_defines_state__ = 1 # this is a lie but good enough for testing. + ... + >>> r=myrational(3, 4) + >>> r + Rational(3, 4) + >>> s=pickle.dumps(r) + + >>> class myworld(world): + ... def __init__(self): + ... world.__init__(self, 'anywhere') + ... self.x = 1 + ... + >>> w = myworld() + >>> w.greet() + 'Hello from anywhere!' + >>> w.__dict__ + {'x': 1} + >>> try: s=pickle.dumps(w) + ... except RuntimeError, err: print err[0] + ... + Incomplete pickle support (__getstate_manages_dict__ not set) + + >>> class myunsafeworld(myworld): + ... __getstate_manages_dict__ = 1 # this is a lie but good enough for testing. + ... + >>> w = myunsafeworld() + >>> w.greet() + 'Hello from anywhere!' + >>> w.__dict__ + {'x': 1} + >>> s=pickle.dumps(w) + Special member attributes. Tests courtesy of Barry Scott >>> class DerivedFromFoo(Foo): @@ -656,10 +721,11 @@ Testing interaction between callbacks, base declarations, and overloading >>> c = CallbackTest() >>> c.testCallback(1) 2 - >>> c.testCallback('foo') - Traceback (innermost last): - File "", line 1, in ? - TypeError: illegal argument type for built-in operation + + >>> try: c.testCallback('foo') + ... except TypeError, err: assert_integer_expected(err) + ... else: print 'no exception' + >>> c.callback(1) 2 >>> c.callback('foo') @@ -678,10 +744,11 @@ Testing interaction between callbacks, base declarations, and overloading -1 >>> r.callback('foo') 'foo 1' - >>> r.testCallback('foo') - Traceback (innermost last): - File "", line 1, in ? - TypeError: illegal argument type for built-in operation + + >>> try: r.testCallback('foo') + ... except TypeError, err: assert_integer_expected(err) + ... else: print 'no exception' + >>> r.testCallback(1) -1 >>> testCallback(r, 1) @@ -1070,9 +1137,49 @@ test methodologies for wrapping functions that return a pointer 3 >>> eo.second 1 -''' -from test import * +======== test [plain] char converters ============== + >>> get_plain_char() + 'x' + >>> use_plain_char('a') + 'aaa' + >>> use_const_plain_char('b') + 'bbbbb' + +======== test std::complex converters ============== + >>> c = dpolar(3, 5) + >>> type(c) + + >>> '%.3g' % (dreal(c)) + '0.851' + >>> '%.3g' % (dimag(c)) + '-2.88' + >>> '%.3g' % (freal(c)) + '0.851' + >>> '%.3g' % (fimag(c)) + '-2.88' + >>> c = fpolar(7, 13) + >>> type(c) + + >>> '%.3g' % (fimag(c)) + '2.94' + >>> '%.3g' % (freal(c)) + '6.35' + >>> '%.3g' % (dimag(c)) + '2.94' + >>> '%.3g' % (dreal(c)) + '6.35' + +''' +#' + +def assert_integer_expected(err): + """Handle a common error report which appears differently in Python 1.5.x and 2.0""" + assert isinstance(err, TypeError) + message = str(err) + assert (message == "illegal argument type for built-in operation" + or message == "an integer is required") + import string import re import sys