summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Heller <theller@ctypes.org>2008-02-13 20:40:44 (GMT)
committerThomas Heller <theller@ctypes.org>2008-02-13 20:40:44 (GMT)
commit13394e907dc452537120b051cb4cdafe4900f334 (patch)
treee13a8e2f903ce01c206dd9c94243d66758d02e0f
parent2bc6b5eb369b70a068f8de8728914467bb35b017 (diff)
downloadcpython-13394e907dc452537120b051cb4cdafe4900f334.zip
cpython-13394e907dc452537120b051cb4cdafe4900f334.tar.gz
cpython-13394e907dc452537120b051cb4cdafe4900f334.tar.bz2
Merged revisions 60767,60768 via svnmerge from
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). ........
-rw-r--r--Doc/library/ctypes.rst5
-rw-r--r--Lib/ctypes/test/test_pickling.py78
-rw-r--r--Modules/_ctypes/_ctypes.c64
-rw-r--r--Modules/_ctypes/callproc.c23
-rw-r--r--Modules/_ctypes/ctypes.h3
-rw-r--r--Modules/_ctypes/stgdict.c2
6 files changed, 174 insertions, 1 deletions
diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst
index bbe4408..3c305ed 100644
--- a/Doc/library/ctypes.rst
+++ b/Doc/library/ctypes.rst
@@ -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
index 0000000..94a14b9
--- /dev/null
+++ b/Lib/ctypes/test/test_pickling.py
@@ -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()
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index a4400cf..e4fa612 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -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;
diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c
index c24cb81..8656569 100644
--- a/Modules/_ctypes/callproc.c
+++ b/Modules/_ctypes/callproc.c
@@ -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},
diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h
index c78a6ea..0e77999 100644
--- a/Modules/_ctypes/ctypes.h
+++ b/Modules/_ctypes/ctypes.h
@@ -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 {
diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c
index 9914cab..0ad5840 100644
--- a/Modules/_ctypes/stgdict.c
+++ b/Modules/_ctypes/stgdict.c
@@ -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) {