PyRustObject: implement tp_dealloc by dropping the rust object

This commit is contained in:
Daniel Grunwald 2015-06-20 00:30:40 +02:00
parent fa32b52392
commit 7c9b96ee90
5 changed files with 92 additions and 15 deletions

View File

@ -30,7 +30,7 @@ pub use self::string::PyUnicode as PyString;
#[cfg(feature="python27-sys")]
pub use self::iterator::PyIterator;
pub use self::boolobject::PyBool;
pub use self::tuple::PyTuple;
pub use self::tuple::{PyTuple, NoArgs};
pub use self::dict::PyDict;
pub use self::list::PyList;
#[cfg(feature="python27-sys")]

View File

@ -123,7 +123,8 @@ impl <'p> PyModule<'p> {
/// sets `new_type.__module__` to this module's name.
/// The new type will be added to this module when `finish()` is called on the builder.
#[cfg(feature="python27-sys")]
pub fn add_type<T>(&self, name: &str) -> ::rustobject::PyRustTypeBuilder<'p, T> {
pub fn add_type<T>(&self, name: &str) -> ::rustobject::PyRustTypeBuilder<'p, T>
where T: 'p + Send {
::rustobject::new_typebuilder_for_module(self, name)
}
}

View File

@ -17,7 +17,9 @@
// DEALINGS IN THE SOFTWARE.
use python::{Python, PythonObject, PythonObjectWithCheckedDowncast, PythonObjectWithTypeObject, ToPythonPointer};
use objects::PyObject;
use conversion::ToPyObject;
use objects::{PyObject, PyTuple, PyDict};
use err::{PyResult, result_from_owned_ptr};
use ffi;
use libc::c_char;
use std;
@ -49,6 +51,17 @@ impl <'p> PyType<'p> {
pub fn is_instance(&self, obj : &PyObject<'p>) -> bool {
unsafe { ffi::PyObject_TypeCheck(obj.as_ptr(), self.as_type_ptr()) != 0 }
}
/// Calls the type object, thus creating a new instance.
/// This is equivalent to the python expression: 'self(*args, **kwargs)'
#[inline]
pub fn call<A: ?Sized>(&self, args: &A, kwargs: Option<&PyDict<'p>>) -> PyResult<'p, PyObject<'p>>
where A: ToPyObject<'p, ObjectType=PyTuple<'p>> {
let py = self.python();
args.with_borrowed_ptr(py, |args| unsafe {
result_from_owned_ptr(py, ffi::PyObject_Call(self.as_ptr(), args, kwargs.as_ptr()))
})
}
}
impl <'p> PartialEq for PyType<'p> {

View File

@ -27,6 +27,10 @@ use err::{self, PyResult};
pub trait PythonBaseObject<'p> : PythonObject<'p> {
/// Gets the size of the object, in bytes.
fn size() -> usize;
/// Calls the rust destructor for the object and frees the memory.
/// This function is the tp_dealloc implementation.
unsafe extern "C" fn dealloc(ptr: *mut ffi::PyObject);
}
impl <'p> PythonBaseObject<'p> for PyObject<'p> {
@ -34,6 +38,10 @@ impl <'p> PythonBaseObject<'p> for PyObject<'p> {
fn size() -> usize {
mem::size_of::<ffi::PyObject>()
}
unsafe extern "C" fn dealloc(ptr: *mut ffi::PyObject) {
((*ffi::Py_TYPE(ptr)).tp_free.unwrap())(ptr as *mut libc::c_void);
}
}
/// A python object that contains a rust value of type T,
@ -56,7 +64,7 @@ impl <'p, T, B> PyRustObject<'p, T, B> where T: 'p, B: PythonBaseObject<'p> {
(B::size() + align - 1) / align * align
}
/// Gets a reference to this object,
/// Gets a reference to this object, but of the base class type.
#[inline]
pub fn base(&self) -> &B {
unsafe { B::unchecked_downcast_borrow_from(&self.obj) }
@ -73,14 +81,20 @@ impl <'p, T, B> PyRustObject<'p, T, B> where T: 'p, B: PythonBaseObject<'p> {
}
}
impl <'p, T, B> PythonBaseObject<'p> for PyRustObject<'p, T, B> where T: 'p, B: PythonBaseObject<'p> {
impl <'p, T, B> PythonBaseObject<'p> for PyRustObject<'p, T, B> where T: 'p + Send, B: PythonBaseObject<'p> {
#[inline]
fn size() -> usize {
PyRustObject::<T, B>::offset() + mem::size_of::<T>()
}
unsafe extern "C" fn dealloc(obj: *mut ffi::PyObject) {
let offset = PyRustObject::<T, B>::offset() as isize;
ptr::read_and_drop((obj as *mut u8).offset(offset) as *mut T);
B::dealloc(obj)
}
}
impl <'p, T, B> Clone for PyRustObject<'p, T, B> where T: 'p, B: PythonBaseObject<'p> {
impl <'p, T, B> Clone for PyRustObject<'p, T, B> where T: 'p + Send, B: PythonBaseObject<'p> {
#[inline]
fn clone(&self) -> Self {
PyRustObject {
@ -91,7 +105,7 @@ impl <'p, T, B> Clone for PyRustObject<'p, T, B> where T: 'p, B: PythonBaseObjec
}
}
impl <'p, T, B> ToPythonPointer for PyRustObject<'p, T, B> where T: 'p, B: PythonBaseObject<'p> {
impl <'p, T, B> ToPythonPointer for PyRustObject<'p, T, B> where T: 'p + Send, B: PythonBaseObject<'p> {
#[inline]
fn as_ptr(&self) -> *mut ffi::PyObject {
self.obj.as_ptr()
@ -103,7 +117,7 @@ impl <'p, T, B> ToPythonPointer for PyRustObject<'p, T, B> where T: 'p, B: Pytho
}
}
impl <'p, T, B> PythonObject<'p> for PyRustObject<'p, T, B> where T: 'p, B: PythonBaseObject<'p> {
impl <'p, T, B> PythonObject<'p> for PyRustObject<'p, T, B> where T: 'p + Send, B: PythonBaseObject<'p> {
#[inline]
fn as_object(&self) -> &PyObject<'p> {
&self.obj
@ -224,26 +238,35 @@ impl <'p, T: 'p> PythonObject<'p> for PyRustType<'p, T> {
#[repr(C)]
#[must_use]
pub struct PyRustTypeBuilder<'p, T, B = PyObject<'p>> where T: 'p, B: PythonBaseObject<'p> {
pub struct PyRustTypeBuilder<'p, T, B = PyObject<'p>> where T: 'p + Send, B: PythonBaseObject<'p> {
type_obj: PyType<'p>,
target_module: Option<PyModule<'p>>,
ht: *mut ffi::PyHeapTypeObject,
phantom: marker::PhantomData<&'p (B, T)>
}
pub fn new_typebuilder_for_module<'p, T>(m: &PyModule<'p>, name: &str) -> PyRustTypeBuilder<'p, T> {
pub fn new_typebuilder_for_module<'p, T>(m: &PyModule<'p>, name: &str) -> PyRustTypeBuilder<'p, T>
where T: 'p + Send {
let b = PyRustTypeBuilder::new(m.python(), name);
if let Ok(mod_name) = m.name() {
b.dict().set_item("__module__", mod_name).ok();
}
PyRustTypeBuilder { target_module: Some(m.clone()), .. b }
}
impl <'p, T> PyRustTypeBuilder<'p, T> where T: 'p {
unsafe extern "C" fn disabled_new
(subtype: *mut ffi::PyTypeObject, args: *mut ffi::PyObject, kwds: *mut ffi::PyObject)
-> *mut ffi::PyObject {
ffi::PyErr_SetString(ffi::PyExc_TypeError,
b"Cannot initialize rust object from python.\0" as *const u8 as *const libc::c_char);
ptr::null_mut()
}
impl <'p, T> PyRustTypeBuilder<'p, T> where T: 'p + Send {
/// Create a new type builder.
pub fn new(py: Python<'p>, name: &str) -> PyRustTypeBuilder<'p, T> {
unsafe {
let obj = ffi::PyType_GenericAlloc(&mut ffi::PyType_Type, 0);
let obj = (ffi::PyType_Type.tp_alloc.unwrap())(&mut ffi::PyType_Type, 0);
if obj.is_null() {
panic!("Out of memory")
}
@ -251,6 +274,7 @@ impl <'p, T> PyRustTypeBuilder<'p, T> where T: 'p {
(*ht).ht_name = PyString::new(py, name.as_bytes()).steal_ptr();
(*ht).ht_type.tp_name = ffi::PyString_AS_STRING((*ht).ht_name);
(*ht).ht_type.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HEAPTYPE;
(*ht).ht_type.tp_new = Some(disabled_new);
PyRustTypeBuilder {
type_obj: PyType::unchecked_downcast_from(PyObject::from_owned_ptr(py, obj)),
target_module: None,
@ -262,7 +286,7 @@ impl <'p, T> PyRustTypeBuilder<'p, T> where T: 'p {
pub fn base<T2, B2>(self, base_type: &PyRustType<'p, T2, B2>)
-> PyRustTypeBuilder<'p, T, PyRustObject<'p, T2, B2>>
where B2: PythonBaseObject<'p>
where T2: 'p + Send, B2: PythonBaseObject<'p>
{
unsafe {
ffi::Py_XDECREF((*self.ht).ht_type.tp_base as *mut ffi::PyObject);
@ -288,11 +312,12 @@ impl <'p, T> PyRustTypeBuilder<'p, T> where T: 'p {
}
}
impl <'p, T, B> PyRustTypeBuilder<'p, T, B> where T: 'p, B: PythonBaseObject<'p> {
impl <'p, T, B> PyRustTypeBuilder<'p, T, B> where T: 'p + Send, B: PythonBaseObject<'p> {
pub fn finish(self) -> PyResult<'p, PyRustType<'p, T, B>> {
let py = self.type_obj.python();
unsafe {
(*self.ht).ht_type.tp_basicsize = PyRustObject::<T, B>::size() as ffi::Py_ssize_t;
(*self.ht).ht_type.tp_dealloc = Some(PyRustObject::<T, B>::dealloc);
try!(err::error_on_minusone(py, ffi::PyType_Ready(self.type_obj.as_type_ptr())))
}
if let Some(m) = self.target_module {

38
tests/rustobject_tests.rs Normal file
View File

@ -0,0 +1,38 @@
extern crate cpython;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use cpython::*;
#[test]
fn rustobject_calls_drop() {
struct MyObj {
drop_called: Arc<AtomicBool>
}
impl Drop for MyObj {
fn drop(&mut self) {
self.drop_called.store(true, Ordering::Relaxed);
}
}
let gil = Python::acquire_gil();
let py = gil.python();
let t = PyRustTypeBuilder::<MyObj>::new(py, "TypeWithDrop").finish().unwrap();
let drop_called = Arc::new(AtomicBool::new(false));
let inst = t.create_instance(MyObj { drop_called: drop_called.clone() });
assert!(drop_called.load(Ordering::Relaxed) == false);
drop(inst);
assert!(drop_called.load(Ordering::Relaxed) == true);
}
#[test]
fn rustobject_no_init_from_python() {
let gil = Python::acquire_gil();
let py = gil.python();
let t = PyRustTypeBuilder::<i32>::new(py, "TypeWithDrop").finish().unwrap();
assert!(t.call(&NoArgs, None).is_err());
}