RE: [C++-sig] Converting CString to/from std::string
-----Original Message----- From: Ralf W. Grosse-Kunstleve [mailto:rwgk@yahoo.com] Sent: 27. mai 2003 08:46 To: c++-sig@python.org Subject: Re: ÆC++-sigÅ Converting CString to/from std::string --- Nikolai Kirsebom <nikolai@micon.no> wrote:
I have a rather large set of C++ classes I want to make accessible (initially only parts of some of the classes) from Python. I cannot change the classes as they are defined and used throughout a large application. Some of the methods take CString as parameters and others return CString values.
Does anyone have suggestion on how I can convert these parameters so that I can use Boost.Python to generate the wrapper code.
What do you want your CStrings to be in Python? Regular Python strings? If you think "yes" remind yourself that Python strings are immutable. I.e. if you have function signatures involving non-const references or pointers to CString as arguments or return values the mapping from C++ CStrings to Python strings will not work very well. More depending on your response. Ralf Some time ago I made a 'socket based' system (using BISON/FLEX for parsing) where I expose C++ objects to python. In this system CStrings are regular Python strings. So as an example I can write: --------------------------------------------- C++: class IF { CString MenuText; CString GetHelpText(); int SetHelpText(CString s); }; In Python: o = IF() # Behind the 'curtains' create a 'link' to a C++ object print o.MenuText s = o.GetHelpText(o.MenuText + "some text") ---------------------------------------------- So I would like something similar. I'm very new to the Boost system and do not really have experience in C++ Template development so I find the Reference Manual somewhat difficult to understand. This may explain if I request 'dead easy' or 'impossible' things. Thanks for any help Nikolai
--- Kirsebom Nikolai <nikolai.kirsebom@siemens.no> wrote:
Some time ago I made a 'socket based' system (using BISON/FLEX for parsing) where I expose C++ objects to python. In this system CStrings are regular Python strings.
Attached is a small, self-contained demo extension module that shows how to do what you want. Here is the corresponding trivial regression test: from sandbx_boost import custom_string assert custom_string.hello() == "Hello world." assert custom_string.size("california") == 10 If you look at the code you will find: 1. A custom to_python converter (easy): custom_string_to_python_str 2. A custom lvalue converter (needs more code): custom_string_from_python_str The custom converters are registered in the global Boost.Python registry near the top of the module initialization function. Once flow control has passed through the registration code the automatic conversions from and to Python strings will work in any module imported in the same process. HTH, Ralf #include <boost/python/module.hpp> #include <boost/python/def.hpp> #include <boost/python/to_python_converter.hpp> namespace sandbx { namespace { class custom_string { public: custom_string() {} custom_string(std::string const& value) : value_(value) {} std::string const& value() const { return value_; } private: std::string value_; }; struct custom_string_to_python_str { static PyObject* convert(custom_string const& s) { return boost::python::incref(boost::python::object(s.value()).ptr()); } }; struct custom_string_from_python_str { custom_string_from_python_str() { boost::python::converter::registry::push_back( &convertible, &construct, boost::python::type_id<custom_string>()); } static void* convertible(PyObject* obj_ptr) { if (!PyString_Check(obj_ptr)) return 0; return obj_ptr; } static void construct( PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data) { const char* value = PyString_AsString(obj_ptr); if (value == 0) boost::python::throw_error_already_set(); void* storage = ( (boost::python::converter::rvalue_from_python_storage<custom_string>*) data)->storage.bytes; new (storage) custom_string(value); data->convertible = storage; } }; custom_string hello() { return custom_string("Hello world."); } std::size_t size(custom_string const& s) { return s.value().size(); } void init_module() { using namespace boost::python; boost::python::to_python_converter< custom_string, custom_string_to_python_str>(); custom_string_from_python_str(); def("hello", hello); def("size", size); } }} // namespace sandbx::<anonymous> BOOST_PYTHON_MODULE(custom_string) { sandbx::init_module(); } __________________________________ Do you Yahoo!? The New Yahoo! Search - Faster. Easier. Bingo. http://search.yahoo.com
Hi guys I just found the mail below, answers one of my questions, and AFAIK the answer for my query is not clearly there in the Boost.Python docos. In fact, porting code from v1 to v2, I think that this should be in the FAQ for the following question: "How do I register automatic conversions to/from a specific C++ class without exposing the C++ class itself to the Python interface?" in v1, I used to do with by declaring to_python and from_python converter methods in the appropriate namespace. The C++ class that was converted was not known to the Python interface at all. I think the example below seems to address my query, I tried it and it works. It would be nice to add this capability to the FAQ or docs. also, maybe I'm going about this wrong, but it seems to perform this task in v1 was cleaner... is there a better way? I'm sure there must be but I just didn't find it. If there is no better way, some C++ code magic to wrap this up and make it look nicer would be a desired feature IMHO. Note that I wouldn't mind wrapping up and exposing the class instead, if the implicit converters would always be triggered such that my users never have to be aware of the existence of the corresponding C++ class; I tried changing my C++ class to add implicit conversions but it doesn't get triggered somehow, here is an example, with a MyPath class that I would like to be automatically convertible from/to a Python string:: class MyPath { public: /*----- member functions -----*/ MyPath(); MyPath( const char* ); operator const char* (); ... In initmodule: class_<MyPath> clsMyPath( "MyPath", init<const char*>() ); implicitly_convertible<const char*, MyPath>(); Then from Python calling:: objin('/some/path') Which is supposed to be able to call a function wrapped in C++:: void objin( MyPath a ) const I get:: Traceback (most recent call last): File "c:/tmp/aaa", line 16, in ? a.objin('/some/path') Boost.Python.ArgumentError: Python argument types in ClassA.objin(ClassA, str) did not match C++ signature: objin(class Dl::PyextModuleA::ClassA {lvalue}, class Dl::PyextModuleA::MyPath) Also, if my function takes a ``const MyPath&``, it behaves the same (but I did not expect this one to work though). any info greatly appreciated. cheers, Ralf W. Grosse-Kunstleve wrote:
--- Kirsebom Nikolai <nikolai.kirsebom@siemens.no> wrote:
Some time ago I made a 'socket based' system (using BISON/FLEX for parsing) where I expose C++ objects to python. In this system CStrings are regular Python strings.
Attached is a small, self-contained demo extension module that shows how to do what you want. Here is the corresponding trivial regression test:
from sandbx_boost import custom_string assert custom_string.hello() == "Hello world." assert custom_string.size("california") == 10
If you look at the code you will find:
1. A custom to_python converter (easy): custom_string_to_python_str
2. A custom lvalue converter (needs more code): custom_string_from_python_str
The custom converters are registered in the global Boost.Python registry near the top of the module initialization function. Once flow control has passed through the registration code the automatic conversions from and to Python strings will work in any module imported in the same process.
HTH, Ralf
#include <boost/python/module.hpp> #include <boost/python/def.hpp> #include <boost/python/to_python_converter.hpp>
namespace sandbx { namespace {
class custom_string { public: custom_string() {} custom_string(std::string const& value) : value_(value) {} std::string const& value() const { return value_; } private: std::string value_; };
struct custom_string_to_python_str { static PyObject* convert(custom_string const& s) { return boost::python::incref(boost::python::object(s.value()).ptr()); } };
struct custom_string_from_python_str { custom_string_from_python_str() { boost::python::converter::registry::push_back( &convertible, &construct, boost::python::type_id<custom_string>()); }
static void* convertible(PyObject* obj_ptr) { if (!PyString_Check(obj_ptr)) return 0; return obj_ptr; }
static void construct( PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data) { const char* value = PyString_AsString(obj_ptr); if (value == 0) boost::python::throw_error_already_set(); void* storage = ( (boost::python::converter::rvalue_from_python_storage<custom_string>*) data)->storage.bytes; new (storage) custom_string(value); data->convertible = storage; } };
custom_string hello() { return custom_string("Hello world."); }
std::size_t size(custom_string const& s) { return s.value().size(); }
void init_module() { using namespace boost::python;
boost::python::to_python_converter< custom_string, custom_string_to_python_str>();
custom_string_from_python_str();
def("hello", hello); def("size", size); }
}} // namespace sandbx::<anonymous>
BOOST_PYTHON_MODULE(custom_string) { sandbx::init_module(); }
__________________________________ Do you Yahoo!? The New Yahoo! Search - Faster. Easier. Bingo. http://search.yahoo.com
Me again. I wrapped up the ugly bits of the automated conversion to do some of the cooking automatically. I use it like this, for an hypothetical MyPath class for which I want to declare automatic conversion to/from a string (without exposing MyPath):: template <> python::object AutoConverter<MyPath>::toObject( MyPath const& s ) { return python::str( s.cstr() ); } template <> void* AutoConverter<MyPath>::convertible( PyObject* obj_ptr ) { if ( !PyString_Check(obj_ptr) ) return 0; return obj_ptr; } template <> void AutoConverter<MyPath>::fromPython( PyObject* obj_ptr, void* memblock ) { const char* value = PyString_AsString( obj_ptr ); if (value == 0) { python::throw_error_already_set(); } new ( memblock ) MyPath( value ); } Then in my initmodule definition I add:: ... AutoConverter<MyPath>(); ... I tested this with MSVC7.1 (2003/.NET) on Windows XP. Works for me, does the gig for now. But somehow, I feel like I'm not doing something right, there must be a better way to do this. I'm sure Dave thought about some way for automatic conversions to occur but I am not finding it. The template file that wraps this up is attached. (Dave: feel free to massage and include the file in attachment to Boost.Python, if you think it's worth anything.) comments appreciated. cheers, Martin Blais wrote:
Hi guys
I just found the mail below, answers one of my questions, and AFAIK the answer for my query is not clearly there in the Boost.Python docos. In fact, porting code from v1 to v2, I think that this should be in the FAQ for the following question:
"How do I register automatic conversions to/from a specific C++ class without exposing the C++ class itself to the Python interface?"
in v1, I used to do with by declaring to_python and from_python converter methods in the appropriate namespace. The C++ class that was converted was not known to the Python interface at all. I think the example below seems to address my query, I tried it and it works.
It would be nice to add this capability to the FAQ or docs.
also, maybe I'm going about this wrong, but it seems to perform this task in v1 was cleaner... is there a better way? I'm sure there must be but I just didn't find it.
If there is no better way, some C++ code magic to wrap this up and make it look nicer would be a desired feature IMHO.
Note that I wouldn't mind wrapping up and exposing the class instead, if the implicit converters would always be triggered such that my users never have to be aware of the existence of the corresponding C++ class; I tried changing my C++ class to add implicit conversions but it doesn't get triggered somehow, here is an example, with a MyPath class that I would like to be automatically convertible from/to a Python string::
class MyPath {
public:
/*----- member functions -----*/
MyPath(); MyPath( const char* ); operator const char* ();
...
In initmodule:
class_<MyPath> clsMyPath( "MyPath", init<const char*>() ); implicitly_convertible<const char*, MyPath>();
Then from Python calling::
objin('/some/path')
Which is supposed to be able to call a function wrapped in C++::
void objin( MyPath a ) const
I get::
Traceback (most recent call last): File "c:/tmp/aaa", line 16, in ? a.objin('/some/path') Boost.Python.ArgumentError: Python argument types in ClassA.objin(ClassA, str) did not match C++ signature: objin(class Dl::PyextModuleA::ClassA {lvalue}, class Dl::PyextModuleA::MyPath)
Also, if my function takes a ``const MyPath&``, it behaves the same (but I did not expect this one to work though).
any info greatly appreciated.
cheers,
Ralf W. Grosse-Kunstleve wrote:
--- Kirsebom Nikolai <nikolai.kirsebom@siemens.no> wrote:
Some time ago I made a 'socket based' system (using BISON/FLEX for parsing) where I expose C++ objects to python. In this system CStrings are regular Python strings.
Attached is a small, self-contained demo extension module that shows how to do what you want. Here is the corresponding trivial regression test:
from sandbx_boost import custom_string assert custom_string.hello() == "Hello world." assert custom_string.size("california") == 10
If you look at the code you will find:
1. A custom to_python converter (easy): custom_string_to_python_str
2. A custom lvalue converter (needs more code): custom_string_from_python_str
The custom converters are registered in the global Boost.Python registry near the top of the module initialization function. Once flow control has passed through the registration code the automatic conversions from and to Python strings will work in any module imported in the same process.
HTH, Ralf
#include <boost/python/module.hpp> #include <boost/python/def.hpp> #include <boost/python/to_python_converter.hpp>
namespace sandbx { namespace {
class custom_string { public: custom_string() {} custom_string(std::string const& value) : value_(value) {} std::string const& value() const { return value_; } private: std::string value_; };
struct custom_string_to_python_str { static PyObject* convert(custom_string const& s) { return boost::python::incref(boost::python::object(s.value()).ptr()); } };
struct custom_string_from_python_str { custom_string_from_python_str() { boost::python::converter::registry::push_back( &convertible, &construct, boost::python::type_id<custom_string>()); }
static void* convertible(PyObject* obj_ptr) { if (!PyString_Check(obj_ptr)) return 0; return obj_ptr; }
static void construct( PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data) { const char* value = PyString_AsString(obj_ptr); if (value == 0) boost::python::throw_error_already_set(); void* storage = (
(boost::python::converter::rvalue_from_python_storage<custom_string>*) data)->storage.bytes; new (storage) custom_string(value); data->convertible = storage; } };
custom_string hello() { return custom_string("Hello world."); }
std::size_t size(custom_string const& s) { return s.value().size(); }
void init_module() { using namespace boost::python;
boost::python::to_python_converter< custom_string, custom_string_to_python_str>();
custom_string_from_python_str();
def("hello", hello); def("size", size); }
}} // namespace sandbx::<anonymous>
BOOST_PYTHON_MODULE(custom_string) { sandbx::init_module(); }
__________________________________ Do you Yahoo!? The New Yahoo! Search - Faster. Easier. Bingo. http://search.yahoo.com
#include <boost/python.hpp> #include <memory> // for auto_ptr template <class T> struct AutoConverter { AutoConverter(); // Conversion "to Python" method. // // If this is not specialized, it calls the toObject() factory which is // expected to return a Boost.Python container type for the Python // representation of the object. static PyObject* convert( T const& s ); // Conversion "to Python" method. // // Override and build and return a Boost.Python container type to represent // the Python object. static python::object toObject( T const& s ); // Specialize the following method to return whether the type conversion can // be applied or not. static void* convertible( PyObject* ); // Main method for "from Python" method--don't override, it contains the // memory allocation. static void construct( PyObject* obj_ptr, python::converter::rvalue_from_python_stage1_data* data ); // Conversion "from Python" method. // // Override this method to convert from the PyObject* into the already // allocated memory block (use in-place allocation). static void fromPython( PyObject* obj_ptr, void* memblock ); // Conversion "from Python" method. // // Override this method to convert from the Boost.Python object into the // already allocated memory block (use in-place allocation). static void fromObject( python::object& obj, void* memblock ); }; template <class T> AutoConverter<T>::AutoConverter() { // Register to-python converter. python::to_python_converter< T, AutoConverter<T> >(); // Register from-python converter. python::converter::registry::push_back( &convertible, &construct, python::type_id<T>() ); } template <class T> PyObject* AutoConverter<T>::convert( T const& s ) { return python::incref( toObject( s ).ptr() ); } template <class T> void* AutoConverter<T>::convertible( PyObject* obj_ptr ) { // Default implementation refuses conversion. return 0; } template <class T> void AutoConverter<T>::construct( PyObject* obj_ptr, python::converter::rvalue_from_python_stage1_data* data ) { // This should be exception-safe if the conversion fails (i.e. auto_ptr will // take care of freeing memory. std::auto_ptr<char> storage( reinterpret_cast< python::converter::rvalue_from_python_storage<T>* >( data )->storage.bytes ); fromPython( obj_ptr, storage.get() ); data->convertible = storage.release(); } template <class T> void AutoConverter<T>::fromPython( PyObject* obj_ptr, void* memblock ) { python::handle<> h( obj_ptr ); python::object obj( h ); fromObject( obj, memblock ); } template <class T> void AutoConverter<T>::fromObject( python::object& obj, void* memblock ) { // This method must be overriden or not called. assert( false ); }
Martin Blais <blais@iro.umontreal.ca> writes:
Me again.
I wrapped up the ugly bits of the automated conversion to do some of the cooking automatically. I use it like this, for an hypothetical MyPath class for which I want to declare automatic conversion to/from a string (without exposing MyPath)::
template <> python::object AutoConverter<MyPath>::toObject( MyPath const& s ) { return python::str( s.cstr() ); }
template <> void* AutoConverter<MyPath>::convertible( PyObject* obj_ptr ) { if ( !PyString_Check(obj_ptr) ) return 0; return obj_ptr; }
template <> void AutoConverter<MyPath>::fromPython( PyObject* obj_ptr, void* memblock ) { const char* value = PyString_AsString( obj_ptr ); if (value == 0) { python::throw_error_already_set(); } new ( memblock ) MyPath( value ); }
Then in my initmodule definition I add::
... AutoConverter<MyPath>(); ...
I tested this with MSVC7.1 (2003/.NET) on Windows XP. Works for me, does the gig for now.
It's an interesting interface, the idea of using member function specialization to provide the functionality. The downside is that a failure to implement the member functions shows up only at runtime. If you left out the default implementations, you could get it to fail at link time, but I'd much rather see a solution that fails at compile time. It's easy enough to do. Another minor issue is that your converters force you to register conversions in both directions (not always desired), and only handle rvalue from-python conversions. That may be what most people want, most of the time... though I'm not sure.
But somehow, I feel like I'm not doing something right, there must be a better way to do this. I'm sure Dave thought about some way for automatic conversions to occur but I am not finding it.
Well, not really anything beyond the low-level facilities your little wrapper takes advantage of.
(Dave: feel free to massage and include the file in attachment to Boost.Python, if you think it's worth anything.)
Well, it's got at least one serious problem:
template <class T> void AutoConverter<T>::construct( PyObject* obj_ptr, python::converter::rvalue_from_python_stage1_data* data ) { // This should be exception-safe if the conversion fails (i.e. auto_ptr will // take care of freeing memory. std::auto_ptr<char> storage( reinterpret_cast< python::converter::rvalue_from_python_storage<T>* >( data )->storage.bytes );
This whole statement is just wrong. You shouldn't be using reinterpret_cast (static_cast through void* or plain-old C-style cast will do) and you *definitely* shouldn't be trying to manage that memory with an auto_ptr. It's owned elsewhere. -- Dave Abrahams Boost Consulting www.boost-consulting.com
David Abrahams wrote:
Martin Blais <blais@iro.umontreal.ca> writes:
It's an interesting interface, the idea of using member function specialization to provide the functionality. The downside is that a failure to implement the member functions shows up only at runtime. If you left out the default implementations, you could get it to fail at link time, but I'd much rather see a solution that fails at compile time. It's easy enough to do.
well, the fact is you have a choice to specialize the method that uses PyObject* or the one that uses/returns boost::python::object. whichever one you decide to use the other one has to be defined (even if not called i think, iirc that's a compiler-dependent behaviour, not sure).
Another minor issue is that your converters force you to register conversions in both directions (not always desired), and only handle rvalue from-python conversions. That may be what most people want, most of the time... though I'm not sure.
i assumed that for that you would use the code you provide. i wonder if we could split in two base classes and mixin.
Well, it's got at least one serious problem:
template <class T> void AutoConverter<T>::construct( PyObject* obj_ptr, python::converter::rvalue_from_python_stage1_data* data ) { // This should be exception-safe if the conversion fails (i.e. auto_ptr will // take care of freeing memory. std::auto_ptr<char> storage( reinterpret_cast< python::converter::rvalue_from_python_storage<T>* >( data )->storage.bytes );
This whole statement is just wrong. You shouldn't be using reinterpret_cast (static_cast through void* or plain-old C-style cast will do) and you *definitely* shouldn't be trying to manage that memory with an auto_ptr. It's owned elsewhere.
you're so totally right, i screwed up, somehow i got into thinking about the in-place allocation later on and i was mentally adding a call to a user-provided function, thinking, it might raise an exception. (and i had not written a test for the case where it fails yet). thanks for your comments! :-) cheers,
Martin Blais <blais@iro.umontreal.ca> writes:
David Abrahams wrote:
Martin Blais <blais@iro.umontreal.ca> writes: It's an interesting interface, the idea of using member function specialization to provide the functionality. The downside is that a failure to implement the member functions shows up only at runtime. If you left out the default implementations, you could get it to fail at link time, but I'd much rather see a solution that fails at compile time. It's easy enough to do.
well, the fact is you have a choice to specialize the method that uses PyObject* or the one that uses/returns boost::python::object. whichever one you decide to use the other one has to be defined (even if not called i think, iirc that's a compiler-dependent behaviour, not sure).
I'm not sure which of my points you're addressing, but it's possible to design an interface that lets you define your to-python converter as returning either object or PyObject*, and fails at compile-time if it's not defined.
Another minor issue is that your converters force you to register conversions in both directions (not always desired), and only handle rvalue from-python conversions. That may be what most people want, most of the time... though I'm not sure.
i assumed that for that you would use the code you provide. i wonder if we could split in two base classes and mixin.
I think that design sounds a little bit too complicated for what it actually accomplishes. Maybe I'll work out a small proposal. template <> python::object AutoConverter<MyPath>::toObject( MyPath const& s ) { return python::str( s.cstr() ); } template <> void* AutoConverter<MyPath>::convertible( PyObject* obj_ptr ) { if ( !PyString_Check(obj_ptr) ) return 0; return obj_ptr; } template <> void AutoConverter<MyPath>::fromPython( PyObject* obj_ptr, void* memblock ) { const char* value = PyString_AsString( obj_ptr ); if (value == 0) { python::throw_error_already_set(); } new ( memblock ) MyPath( value ); } Then in my initmodule definition I add:: ... AutoConverter<MyPath>(); ... -- Dave Abrahams Boost Consulting www.boost-consulting.com
participants (4)
-
David Abrahams -
Kirsebom Nikolai -
Martin Blais -
Ralf W. Grosse-Kunstleve