[Python-checkins] r60767 - in python/trunk: Doc/library/ctypes.rst Lib/ctypes/test/test_pickling.py Misc/NEWS Modules/_ctypes/_ctypes.c Modules/_ctypes/callproc.c Modules/_ctypes/ctypes.h Modules/_ctypes/stgdict.c

thomas.heller python-checkins at python.org
Wed Feb 13 21:21:54 CET 2008


Author: thomas.heller
Date: Wed Feb 13 21:21:53 2008
New Revision: 60767

Added:
   python/trunk/Lib/ctypes/test/test_pickling.py   (contents, props changed)
Modified:
   python/trunk/Doc/library/ctypes.rst
   python/trunk/Misc/NEWS
   python/trunk/Modules/_ctypes/_ctypes.c
   python/trunk/Modules/_ctypes/callproc.c
   python/trunk/Modules/_ctypes/ctypes.h
   python/trunk/Modules/_ctypes/stgdict.c
Log:
Add pickle support to ctypes types.

Modified: python/trunk/Doc/library/ctypes.rst
==============================================================================
--- python/trunk/Doc/library/ctypes.rst	(original)
+++ python/trunk/Doc/library/ctypes.rst	Wed Feb 13 21:21:53 2008
@@ -2011,6 +2011,11 @@
    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:
 
 

Added: python/trunk/Lib/ctypes/test/test_pickling.py
==============================================================================
--- (empty file)
+++ python/trunk/Lib/ctypes/test/test_pickling.py	Wed Feb 13 21:21:53 2008
@@ -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(buffer(src),
+                                 buffer(dst))
+
+    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(buffer(y),
+                             buffer(x))
+
+    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()

Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Wed Feb 13 21:21:53 2008
@@ -400,6 +400,9 @@
 Library
 -------
 
+- ctypes instances that are not or do not contain pointers can now be
+  pickled.
+
 - Patch #1966: Break infinite loop in httplib when the servers
   implements the chunked encoding incorrectly.
 

Modified: python/trunk/Modules/_ctypes/_ctypes.c
==============================================================================
--- python/trunk/Modules/_ctypes/_ctypes.c	(original)
+++ python/trunk/Modules/_ctypes/_ctypes.c	Wed Feb 13 21:21:53 2008
@@ -128,6 +128,9 @@
 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;
 
@@ -718,6 +721,7 @@
 	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 @@
 
 	itemalign = itemdict->align;
 
+	if (itemdict->flags & (TYPEFLAG_ISPOINTER | TYPEFLAG_HASPOINTER))
+		stgdict->flags |= TYPEFLAG_HASPOINTER;
+
 	stgdict->size = itemsize * length;
 	stgdict->align = itemalign;
 	stgdict->length = length;
@@ -1684,12 +1691,21 @@
 		switch (PyString_AS_STRING(proto)[0]) {
 		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;
@@ -1926,7 +1942,7 @@
 		    "class must define _flags_ which must be an integer");
 		return -1;
 	}
-	stgdict->flags = PyInt_AS_LONG(ob);
+	stgdict->flags = PyInt_AS_LONG(ob) | TYPEFLAG_ISPOINTER;
 
 	/* _argtypes_ is optional... */
 	ob = PyDict_GetItemString((PyObject *)stgdict, "_argtypes_");
@@ -2001,6 +2017,7 @@
 		return NULL;
 
 	stgdict->paramfunc = CFuncPtrType_paramfunc;
+	stgdict->flags |= TYPEFLAG_ISPOINTER;
 
 	/* create the new instance (which is a class,
 	   since we are a metatype!) */
@@ -2255,6 +2272,45 @@
 	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.
  */
@@ -2267,6 +2323,8 @@
 
 static PyMethodDef CData_methods[] = {
 	{ "__ctypes_from_outparam__", CData_from_outparam, METH_NOARGS, },
+	{ "__reduce__", CData_reduce, METH_NOARGS, },
+	{ "__setstate__", CData_setstate, METH_VARARGS, },
 	{ NULL, NULL },
 };
 
@@ -5107,6 +5165,10 @@
 	if (!m)
 		return;
 
+	_unpickle = PyObject_GetAttrString(m, "_unpickle");
+	if (_unpickle == NULL)
+		return;
+
 	if (PyType_Ready(&PyCArg_Type) < 0)
 		return;
 

Modified: python/trunk/Modules/_ctypes/callproc.c
==============================================================================
--- python/trunk/Modules/_ctypes/callproc.c	(original)
+++ python/trunk/Modules/_ctypes/callproc.c	Wed Feb 13 21:21:53 2008
@@ -1578,7 +1578,30 @@
 	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},

Modified: python/trunk/Modules/_ctypes/ctypes.h
==============================================================================
--- python/trunk/Modules/_ctypes/ctypes.h	(original)
+++ python/trunk/Modules/_ctypes/ctypes.h	Wed Feb 13 21:21:53 2008
@@ -286,6 +286,9 @@
 #define FUNCFLAG_HRESULT 0x2
 #define FUNCFLAG_PYTHONAPI 0x4
 
+#define TYPEFLAG_ISPOINTER 0x100
+#define TYPEFLAG_HASPOINTER 0x200
+
 #define DICTFLAG_FINAL 0x1000
 
 struct tagPyCArgObject {

Modified: python/trunk/Modules/_ctypes/stgdict.c
==============================================================================
--- python/trunk/Modules/_ctypes/stgdict.c	(original)
+++ python/trunk/Modules/_ctypes/stgdict.c	Wed Feb 13 21:21:53 2008
@@ -414,6 +414,8 @@
 			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) {


More information about the Python-checkins mailing list