From 13394e907dc452537120b051cb4cdafe4900f334 Mon Sep 17 00:00:00 2001 From: Thomas Heller Date: Wed, 13 Feb 2008 20:40:44 +0000 Subject: 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). ........ --- Doc/library/ctypes.rst | 5 +++ Lib/ctypes/test/test_pickling.py | 78 ++++++++++++++++++++++++++++++++++++++++ Modules/_ctypes/_ctypes.c | 64 ++++++++++++++++++++++++++++++++- Modules/_ctypes/callproc.c | 23 ++++++++++++ Modules/_ctypes/ctypes.h | 3 ++ Modules/_ctypes/stgdict.c | 2 ++ 6 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 Lib/ctypes/test/test_pickling.py 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) { -- cgit v0.12