Posts tagged ‘rvalue_converter’

Tak jak zapowiadałem w moim poprzednim wpisie, rvalue_converter operujący na streamach jest też, a jakże. Krótkie wprowadzenie: mamy sobie wyeksportowane api, które m.in. prowadzi zapis danych do pliku, ale aby odciążyć sama metodę od żmudnych operacji na strumieniach, a jednocześnie dać jej dużą elastyczność dot. zapisu danych, dostaje ona gotową referencję do strumienia jako parametr, przykładowy kod może wyglądać tak:

class MyClass {
public:
writeToStream( std::ifstream& out ){}
}

No i teraz, fajnie by było mieć możliwość otwarcia pliku po stronie pythona i tylko przesłania samego uchwytu do naszej metody c++’owej. Otóż Ci którzy czytali poprzedni wpis o konwersji stringów już pewnie wiedzą, że to jest możliwe, może troche karkołomne, bo synchronizacja streamów to sprawa niebalna, no ale w moich testach wszystko działa poprawnie.

A oto i kod:

namespace tulips { 
namespace converters { 
namespace fstream {

  class custom_fstream
  {
    public:
		custom_fstream(
			const char* path,
			FILE* file,
			std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out
		) :
			m_fstream(path,mode),
			m_file(file)
		{
			fflush(m_file);
			m_fstream.seekg(ftell(m_file));
		}
		std::fstream& get_stream() { return m_fstream; }
		~custom_fstream(){ fseek( m_file, m_fstream.tellg(), SEEK_SET );}
    private:
      std::fstream m_fstream;
      FILE* m_file;
  };

  struct custom_fstream_from_python_file
  {
	custom_fstream_from_python_file()
	{
		bp::converter::registry::push_back(
			&convertible,
			&construct,
			bp::type_id<custom_fstream>());
	}

	static void* convertible(PyObject* obj_ptr)
	{
		if (!PyFile_Check(obj_ptr) || obj_ptr == Py_None) return 0;
		return obj_ptr;
	}

	static void construct(PyObject* obj_ptr, bp::converter::rvalue_from_python_stage1_data* data)
	{
		FILE* file = PyFile_AsFile(obj_ptr);
		void* storage = ((bp::converter::rvalue_from_python_storage<custom_fstream>*)data)->storage.bytes;
		new (storage) custom_fstream(PyString_AsString(PyFile_Name(obj_ptr)), file, std::ios::in|std::ios::out);
		data->convertible = storage;
	}
  };

  void init_converter()
  {
    custom_fstream_from_python_file();
  }

}}} // namespace tulips::converters::stream

No i teraz już możemy sobie przesyłać uchwyt do strumienia otwarty po stronie pythona 🙂

Przypuśćmy, że mamy gotowy program w c++, który sobie wczytuje pewne dane, operuje na nich i wypluwa wynik, nic wielkiego, przykładów można znaleźć tysiące. Jednak nasz program, żeby być w miarę elastycznym obsługuje pluginy w formie plików bibliotecznych (dll/so), ładowanych na żądanie, ale chcielibyśmy skorzystać z elastyczności jaką daje język skryptowy, np. python a jednocześnie cały czas mieć możliwość optymalizacji kodu po stronie c++.

Rozwiązaniem jest biblioteka boost::python, ale podobnie jak w przypadku boost::shared_ptr dokumentacje mają niezłą, więc wprowadzenia robić nie będę.

Chciałbym się skupić na konwesji typów pomiędzy c++ a python’em i odwrotnie a konkretnie przedstawić konwerter pomiędzy std::basic_string<unsgned char>, który jest wykorzystywany do przechowywania danych wewnątrz elementu (np. Field) z i do pythonowego stringa, mamy już wyeksportowane api, które może mieć postać:

class SampleObject {
public:
void setValue( std::basic_string v& ) {
// do some important stuff
}
}

Jednak po wyeksportowaniu takiej metody okazuje się, że wołając z pythona:sampleObject.setValue( ‚sample value’ )Dostajemy błąd o niezgodności typów i pozostaje nam tylko przyglądać sie komunikatowi błędu.

Rozwiązaniem jest napisanie własnego konwertera, który weźmie stringa pythonowego, zamieni go na unsigned char* i przekaże dalej gdzie trzeba. Oczywiście nie jest to jedyne rozwiązanie, bo równie dobrze można sobie przeciążyć dana metode we wrapperze do api pythonowym, i w niej zrobić całe czary-mary, ale to takie szablonowe…

No więc nasz konwerter, wie że dostanie PyString albo std::basic_string<unsigned char> i ma wyprodukować to co trzeba, oto i on:

typedef std::basic_string ustring;
namespace bp = boost::python;
namespace tulips{
namespace converters {
namespace ustr {
// definiujemy zawijacza na ustring
class custom_string {
public:
custom_string() {}
custom_string(const ustring& value) :
 	m_string(value) {
}
const ustring& value() const {
return m_string;
}
private:
ustring m_string;
};
struct custom_string_to_python_str {
static PyObject* convert(const custom_string& s) {
return bp::incref(bp::object(s.value()).ptr());
}
};
struct custom_string_from_python_str {
custom_string_from_python_str() {
bp::converter::registry::push_back(&convertible, &construct, bp::type_id());
}// sprawdz czy konwersja jest mozliwa
static void* convertible(PyObject* obj_ptr) {
if (!PyString_Check(obj_ptr))
return 0; //zero 🙂
return obj_ptr;
}// no i przekonwertuj
static void construct(PyObject* obj_ptr, bp::converter::rvalue_from_python_stage1_data* data) {

// pobierze stringa z obiektu PyObject
const char* str = PyString_AsString(obj_ptr);
const unsigned char* value = (const unsigned char*) str; // cast na unsigned char*
if (value == 0) // sprawdz czy napewno sie udał
bp::throw_error_already_set();
void* storage = ((bp::converter::rvalue_from_python_storage*) data)->storage.bytes; // pobierz pamiec zalakowana w storage'u
new (storage) custom_string(value); // przypisz custom_string'a do pamieci
data->convertible = storage; // zapisz przekonwertowany typ
}
};
// jeszcze tylko pobierzemy sobie rozmiar to tak zeby len() tez zadzialalo
std::size_t size(const custom_string& s) {
return s.value().size();
}
// no i trzeba powiadomic pythona jak ma przeprowadzic konwersje
void init_converter() {
bp::to_python_converter();

custom_string_from_python_str();

bp::def("size", size);
}}
}
} // namespace tulips::converters::ustring

Voila, to nie było aż takie trudne!