Commits

Armin Rigo  committed 0143876

issue 87: first stab

  • Participants
  • Parent commits 708b37a

Comments (0)

Files changed (4)

File c/_cffi_backend.c

 #define CT_IS_LONGDOUBLE        65536
 #define CT_IS_BOOL             131072
 #define CT_IS_FILE             262144
+#define CT_IS_VOID_PTR         524288
 #define CT_PRIMITIVE_ANY  (CT_PRIMITIVE_SIGNED |        \
                            CT_PRIMITIVE_UNSIGNED |      \
                            CT_PRIMITIVE_CHAR |          \
     if (cd->c_type->ct_flags & CT_IS_PTR_TO_OWNED) {
         Py_DECREF(((CDataObject_own_structptr *)cd)->structobj);
     }
+    else if (cd->c_type->ct_flags & CT_IS_VOID_PTR) {
+        PyObject *x = (PyObject *)(cd->c_data + 42);
+        Py_DECREF(x);
+    }
     else if (cd->c_type->ct_flags & CT_FUNCTIONPTR) {
         /* a callback */
         ffi_closure *closure = (ffi_closure *)cd->c_data;
     return result;
 }
 
+static PyObject *_cdata_repr2(CDataObject *cd, char *text, PyObject *x)
+{
+    PyObject *res, *s = PyObject_Repr(x);
+    if (s == NULL)
+        return NULL;
+    res = PyText_FromFormat("<cdata '%s' %s %s>",
+                            cd->c_type->ct_name, text, PyText_AsUTF8(s));
+    Py_DECREF(s);
+    return res;
+}
+
 static PyObject *cdataowning_repr(CDataObject *cd)
 {
     Py_ssize_t size;
-    if (cd->c_type->ct_flags & CT_POINTER)
+    if (cd->c_type->ct_flags & CT_POINTER) {
+        if (cd->c_type->ct_flags & CT_IS_VOID_PTR)
+            goto handle_repr;
         size = cd->c_type->ct_itemdescr->ct_size;
+    }
     else if (cd->c_type->ct_flags & CT_ARRAY)
         size = get_array_length(cd) * cd->c_type->ct_itemdescr->ct_size;
     else if (cd->c_type->ct_flags & CT_FUNCTIONPTR)
 
  callback_repr:
     {
-        PyObject *s, *res;
         PyObject *args = (PyObject *)((ffi_closure *)cd->c_data)->user_data;
         if (args == NULL)
             return cdata_repr(cd);
-
-        s = PyObject_Repr(PyTuple_GET_ITEM(args, 1));
-        if (s == NULL)
-            return NULL;
-        res = PyText_FromFormat("<cdata '%s' calling %s>",
-                                cd->c_type->ct_name, PyText_AsUTF8(s));
-        Py_DECREF(s);
-        return res;
+        else
+            return _cdata_repr2(cd, "calling", PyTuple_GET_ITEM(args, 1));
+    }
+
+ handle_repr:
+    {
+        PyObject *x = (PyObject *)(cd->c_data + 42);
+        return _cdata_repr2(cd, "handle to", x);
     }
 }
 
     td->ct_flags = CT_POINTER;
     if (ctitem->ct_flags & (CT_STRUCT|CT_UNION))
         td->ct_flags |= CT_IS_PTR_TO_OWNED;
+    if (ctitem->ct_flags & CT_VOID)
+        td->ct_flags |= CT_IS_VOID_PTR;
     if ((ctitem->ct_flags & CT_VOID) ||
         ((ctitem->ct_flags & CT_PRIMITIVE_CHAR) &&
          ctitem->ct_size == sizeof(char)))
     return Py_None;
 }
 
+static PyObject *b_newp_handle(PyObject *self, PyObject *args)
+{
+    CTypeDescrObject *ct;
+    CDataObject *cd;
+    PyObject *x;
+    if (!PyArg_ParseTuple(args, "O!O", &CTypeDescr_Type, &ct, &x))
+        return NULL;
+
+    if (!(ct->ct_flags & CT_IS_VOID_PTR)) {
+        PyErr_Format(PyExc_TypeError, "needs 'void *', got '%s'", ct->ct_name);
+        return NULL;
+    }
+
+    cd = allocate_owning_object(sizeof(CDataObject), ct);
+    if (cd == NULL)
+        return NULL;
+
+    Py_INCREF(x);
+    cd->c_data = ((char *)x) - 42;
+    return (PyObject *)cd;
+}
+
+static PyObject *b_from_handle(PyObject *self, PyObject *arg)
+{
+    CTypeDescrObject *ct;
+    char *raw;
+    PyObject *x;
+    if (!CData_Check(arg)) {
+        PyErr_SetString(PyExc_TypeError, "expected a 'cdata' object");
+        return NULL;
+    }
+    ct = ((CDataObject *)arg)->c_type;
+    raw = ((CDataObject *)arg)->c_data;
+    if (!(ct->ct_flags & CT_CAST_ANYTHING)) {
+        PyErr_Format(PyExc_TypeError,
+                     "expected a 'cdata' object with a 'void *' out of "
+                     "new_handle(), got '%s'", ct->ct_name);
+        return NULL;
+    }
+    if (!raw) {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "cannot use from_handle() on NULL pointer");
+        return NULL;
+    }
+    x = (PyObject *)(raw + 42);
+    Py_INCREF(x);
+    return x;
+}
+
 static PyObject *b__get_types(PyObject *self, PyObject *noarg)
 {
     return PyTuple_Pack(2, (PyObject *)&CData_Type,
     {"buffer", b_buffer, METH_VARARGS},
     {"get_errno", b_get_errno, METH_NOARGS},
     {"set_errno", b_set_errno, METH_VARARGS},
+    {"newp_handle", b_newp_handle, METH_VARARGS},
+    {"from_handle", b_from_handle, METH_O},
     {"_get_types", b__get_types, METH_NOARGS},
     {"_testfunc", b__testfunc, METH_VARARGS},
     {NULL,     NULL}    /* Sentinel */
     assert x != cast(BIntP, 12344)
     assert hash(x) == hash(cast(BIntP, 12345))
 
+def test_new_handle():
+    import _weakref
+    BVoidP = new_pointer_type(new_void_type())
+    BCharP = new_pointer_type(new_primitive_type("char"))
+    class mylist(list):
+        pass
+    o = mylist([2, 3, 4])
+    x = newp_handle(BVoidP, o)
+    assert repr(x) == "<cdata 'void *' handle to [2, 3, 4]>"
+    assert x
+    assert from_handle(x) is o
+    assert from_handle(cast(BCharP, x)) is o
+    wr = _weakref.ref(o)
+    del o
+    import gc; gc.collect()
+    assert wr() is not None
+    assert from_handle(x) == list((2, 3, 4))
+    assert from_handle(cast(BCharP, x)) == list((2, 3, 4))
+    del x
+    for i in range(3):
+        if wr() is not None:
+            import gc; gc.collect()
+    assert wr() is None
+    py.test.raises(RuntimeError, from_handle, cast(BCharP, 0))
+
+
 def test_version():
     # this test is here mostly for PyPy
     assert __version__ == "0.7"
             if name.startswith('RTLD_'):
                 setattr(self, name, getattr(backend, name))
         #
-        BVoidP = self._get_cached_btype(model.voidp_type)
+        self.BVoidP = self._get_cached_btype(model.voidp_type)
         if isinstance(backend, types.ModuleType):
             # _cffi_backend: attach these constants to the class
             if not hasattr(FFI, 'NULL'):
-                FFI.NULL = self.cast(BVoidP, 0)
+                FFI.NULL = self.cast(self.BVoidP, 0)
                 FFI.CData, FFI.CType = backend._get_types()
         else:
             # ctypes backend: attach these constants to the instance
-            self.NULL = self.cast(BVoidP, 0)
+            self.NULL = self.cast(self.BVoidP, 0)
             self.CData, self.CType = backend._get_types()
 
     def cdef(self, csource, override=False):
         self._cdefsources.extend(ffi_to_include._cdefsources)
         self._cdefsources.append(']')
 
+    def new_handle(self, x):
+        return self._backend.newp_handle(self.BVoidP, x)
+
+    def from_handle(self, x):
+        return self._backend.from_handle(x)
+
 
 def _make_ffi_library(ffi, libname, flags):
     import os

File testing/test_ffi_backend.py

         assert ffi.typeof("long(*)(long, long**, ...)").cname == (
             "long(*)(long, long * *, ...)")
         assert ffi.typeof("long(*)(long, long**, ...)").ellipsis is True
+
+    def test_new_handle(self):
+        ffi = FFI(backend=self.Backend())
+        o = [2, 3, 4]
+        p = ffi.new_handle(o)
+        assert ffi.typeof(p) == ffi.typeof("void *")
+        assert ffi.from_handle(p) is o
+        assert ffi.from_handle(ffi.cast("char *", p)) is o
+        py.test.raises(RuntimeError, ffi.from_handle, ffi.NULL)