]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Merged revisions 60767,60768 via svnmerge from
authorThomas Heller <theller@ctypes.org>
Wed, 13 Feb 2008 20:40:44 +0000 (20:40 +0000)
committerThomas Heller <theller@ctypes.org>
Wed, 13 Feb 2008 20:40:44 +0000 (20:40 +0000)
svn+ssh://pythondev@svn.python.org/python/trunk

........
  r60767 | thomas.heller | 2008-02-13 21:21:53 +0100 (Mi, 13 Feb 2008) | 1 line

  Add pickle support to ctypes types.
........
  r60768 | thomas.heller | 2008-02-13 21:36:51 +0100 (Mi, 13 Feb 2008) | 1 line

  Make the test somewhat clearer (I hope).
........

Doc/library/ctypes.rst
Lib/ctypes/test/test_pickling.py [new file with mode: 0644]
Modules/_ctypes/_ctypes.c
Modules/_ctypes/callproc.c
Modules/_ctypes/ctypes.h
Modules/_ctypes/stgdict.c

index bbe44089e7ba4ea707e6cde8e1744c00bce74e9c..3c305ed39a77138ee3b9f304ba3dff7d25d0feae 100644 (file)
@@ -2009,6 +2009,11 @@ Fundamental data types
    ctypes data types.  ``_SimpleCData`` is a subclass of ``_CData``, so it inherits
    their methods and attributes.
 
+   .. versionchanged:: 2.6
+
+      ctypes data types that are not and do not contain pointers can
+      now be pickled.
+
 Instances have a single attribute:
 
 
diff --git a/Lib/ctypes/test/test_pickling.py b/Lib/ctypes/test/test_pickling.py
new file mode 100644 (file)
index 0000000..94a14b9
--- /dev/null
@@ -0,0 +1,78 @@
+import unittest
+import pickle
+from ctypes import *
+import _ctypes_test
+dll = CDLL(_ctypes_test.__file__)
+
+class X(Structure):
+    _fields_ = [("a", c_int), ("b", c_double)]
+    init_called = 0
+    def __init__(self, *args, **kw):
+        X.init_called += 1
+        self.x = 42
+
+class Y(X):
+    _fields_ = [("str", c_char_p)]
+
+class PickleTest(unittest.TestCase):
+    def dumps(self, item):
+        return pickle.dumps(item)
+
+    def loads(self, item):
+        return pickle.loads(item)
+
+    def test_simple(self):
+        for src in [
+            c_int(42),
+            c_double(3.14),
+            ]:
+            dst = self.loads(self.dumps(src))
+            self.failUnlessEqual(src.__dict__, dst.__dict__)
+            self.failUnlessEqual(memoryview(src).tobytes(),
+                                 memoryview(dst).tobytes())
+
+    def test_struct(self):
+        X.init_called = 0
+
+        x = X()
+        x.a = 42
+        self.failUnlessEqual(X.init_called, 1)
+
+        y = self.loads(self.dumps(x))
+
+        # loads must NOT call __init__
+        self.failUnlessEqual(X.init_called, 1)
+
+        # ctypes instances are identical when the instance __dict__
+        # and the memory buffer are identical
+        self.failUnlessEqual(y.__dict__, x.__dict__)
+        self.failUnlessEqual(memoryview(y).tobytes(),
+                             memoryview(x).tobytes())
+
+    def test_unpickable(self):
+        # ctypes objects that are pointers or contain pointers are
+        # unpickable.
+        self.assertRaises(ValueError, lambda: self.dumps(Y()))
+
+        prototype = CFUNCTYPE(c_int)
+
+        for item in [
+            c_char_p(),
+            c_wchar_p(),
+            c_void_p(),
+            pointer(c_int(42)),
+            dll._testfunc_p_p,
+            prototype(lambda: 42),
+            ]:
+            self.assertRaises(ValueError, lambda: self.dumps(item))
+
+class PickleTest_1(PickleTest):
+    def dumps(self, item):
+        return pickle.dumps(item, 1)
+
+class PickleTest_2(PickleTest):
+    def dumps(self, item):
+        return pickle.dumps(item, 2)
+
+if __name__ == "__main__":
+    unittest.main()
index a4400cf20e7705632c9d0f1a125ee9e6dd17216c..e4fa612a06f109634c70c7a547df136ac3fff41e 100644 (file)
@@ -123,6 +123,9 @@ bytes(cdata)
 PyObject *PyExc_ArgError;
 static PyTypeObject Simple_Type;
 
+/* a callable object used for unpickling */
+static PyObject *_unpickle;
+
 char *conversion_mode_encoding = NULL;
 char *conversion_mode_errors = NULL;
 
@@ -710,6 +713,7 @@ PointerType_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
        stgdict->length = 1;
        stgdict->ffi_type_pointer = ffi_type_pointer;
        stgdict->paramfunc = PointerType_paramfunc;
+       stgdict->flags |= TYPEFLAG_ISPOINTER;
 
        proto = PyDict_GetItemString(typedict, "_type_"); /* Borrowed ref */
        if (proto && -1 == PointerType_SetProto(stgdict, proto)) {
@@ -1139,6 +1143,9 @@ ArrayType_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 
        itemalign = itemdict->align;
 
+       if (itemdict->flags & (TYPEFLAG_ISPOINTER | TYPEFLAG_HASPOINTER))
+               stgdict->flags |= TYPEFLAG_HASPOINTER;
+
        stgdict->size = itemsize * length;
        stgdict->align = itemalign;
        stgdict->length = length;
@@ -1706,12 +1713,21 @@ SimpleType_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
                switch (*proto_str) {
                case 'z': /* c_char_p */
                        ml = &c_char_p_method;
+                       stgdict->flags |= TYPEFLAG_ISPOINTER;
                        break;
                case 'Z': /* c_wchar_p */
                        ml = &c_wchar_p_method;
+                       stgdict->flags |= TYPEFLAG_ISPOINTER;
                        break;
                case 'P': /* c_void_p */
                        ml = &c_void_p_method;
+                       stgdict->flags |= TYPEFLAG_ISPOINTER;
+                       break;
+               case 'u':
+               case 'X':
+               case 'O':
+                       ml = NULL;
+                       stgdict->flags |= TYPEFLAG_ISPOINTER;
                        break;
                default:
                        ml = NULL;
@@ -1928,7 +1944,7 @@ make_funcptrtype_dict(StgDictObject *stgdict)
                    "class must define _flags_ which must be an integer");
                return -1;
        }
-       stgdict->flags = PyLong_AS_LONG(ob);
+       stgdict->flags = PyLong_AS_LONG(ob) | TYPEFLAG_ISPOINTER;
 
        /* _argtypes_ is optional... */
        ob = PyDict_GetItemString((PyObject *)stgdict, "_argtypes_");
@@ -2003,6 +2019,7 @@ CFuncPtrType_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
                return NULL;
 
        stgdict->paramfunc = CFuncPtrType_paramfunc;
+       stgdict->flags |= TYPEFLAG_ISPOINTER;
 
        /* create the new instance (which is a class,
           since we are a metatype!) */
@@ -2235,6 +2252,45 @@ CData_nohash(PyObject *self)
        return -1;
 }
 
+static PyObject *
+CData_reduce(PyObject *_self, PyObject *args)
+{
+       CDataObject *self = (CDataObject *)_self;
+
+       if (PyObject_stgdict(_self)->flags & (TYPEFLAG_ISPOINTER|TYPEFLAG_HASPOINTER)) {
+               PyErr_SetString(PyExc_ValueError,
+                               "ctypes objects containing pointers cannot be pickled");
+               return NULL;
+       }
+       return Py_BuildValue("O(O(NN))",
+                            _unpickle,
+                            Py_TYPE(_self),
+                            PyObject_GetAttrString(_self, "__dict__"),
+                            PyString_FromStringAndSize(self->b_ptr, self->b_size));
+}
+
+static PyObject *
+CData_setstate(PyObject *_self, PyObject *args)
+{
+       void *data;
+       int len;
+       int res;
+       PyObject *dict, *mydict;
+       CDataObject *self = (CDataObject *)_self;
+       if (!PyArg_ParseTuple(args, "Os#", &dict, &data, &len))
+               return NULL;
+       if (len > self->b_size)
+               len = self->b_size;
+       memmove(self->b_ptr, data, len);
+       mydict = PyObject_GetAttrString(_self, "__dict__");
+       res = PyDict_Update(mydict, dict);
+       Py_DECREF(mydict);
+       if (res == -1)
+               return NULL;
+       Py_INCREF(Py_None);
+       return Py_None;
+}
+
 /*
  * default __ctypes_from_outparam__ method returns self.
  */
@@ -2247,6 +2303,8 @@ CData_from_outparam(PyObject *self, PyObject *args)
 
 static PyMethodDef CData_methods[] = {
        { "__ctypes_from_outparam__", CData_from_outparam, METH_NOARGS, },
+       { "__reduce__", CData_reduce, METH_NOARGS, },
+       { "__setstate__", CData_setstate, METH_VARARGS, },
        { NULL, NULL },
 };
 
@@ -4934,6 +4992,10 @@ init_ctypes(void)
        if (!m)
                return;
 
+       _unpickle = PyObject_GetAttrString(m, "_unpickle");
+       if (_unpickle == NULL)
+               return;
+
        if (PyType_Ready(&PyCArg_Type) < 0)
                return;
 
index c24cb815979ceacab411f621177fd7850a2357f0..8656569cfc3b6ad8dbb71018d8af263816376b29 100644 (file)
@@ -1543,7 +1543,30 @@ resize(PyObject *self, PyObject *args)
        return Py_None;
 }
 
+static PyObject *
+unpickle(PyObject *self, PyObject *args)
+{
+       PyObject *typ;
+       PyObject *state;
+       PyObject *result;
+       PyObject *tmp;
+
+       if (!PyArg_ParseTuple(args, "OO", &typ, &state))
+               return NULL;
+       result = PyObject_CallMethod(typ, "__new__", "O", typ);
+       if (result == NULL)
+               return NULL;
+       tmp = PyObject_CallMethod(result, "__setstate__", "O", state);
+       if (tmp == NULL) {
+               Py_DECREF(result);
+               return NULL;
+       }
+       Py_DECREF(tmp);
+       return result;
+}
+
 PyMethodDef module_methods[] = {
+       {"_unpickle", unpickle, METH_VARARGS },
        {"resize", resize, METH_VARARGS, "Resize the memory buffer of a ctypes instance"},
 #ifdef CTYPES_UNICODE
        {"set_conversion_mode", set_conversion_mode, METH_VARARGS, set_conversion_mode_doc},
index c78a6ea3a52e3b6f56b7dc659bdde57d45de03ef..0e7799983f4efa0ac2e4f9173407c5d38c3a1730 100644 (file)
@@ -269,6 +269,9 @@ PyObject *_CallProc(PPROC pProc,
 #define FUNCFLAG_HRESULT 0x2
 #define FUNCFLAG_PYTHONAPI 0x4
 
+#define TYPEFLAG_ISPOINTER 0x100
+#define TYPEFLAG_HASPOINTER 0x200
+
 #define DICTFLAG_FINAL 0x1000
 
 struct tagPyCArgObject {
index 9914cab36ce8867d6eda7ab858fa690e6221ffa8..0ad584033679bfdf54b400d7495bca3f18201bd0 100644 (file)
@@ -402,6 +402,8 @@ StructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct)
                        return -1;
                }
                stgdict->ffi_type_pointer.elements[ffi_ofs + i] = &dict->ffi_type_pointer;
+               if (dict->flags & (TYPEFLAG_ISPOINTER | TYPEFLAG_HASPOINTER))
+                       stgdict->flags |= TYPEFLAG_HASPOINTER;
                dict->flags |= DICTFLAG_FINAL; /* mark field type final */
                if (PyTuple_Size(pair) == 3) { /* bits specified */
                        switch(dict->ffi_type_pointer.type) {