Add pickle support to ctypes types.

This commit is contained in:
Thomas Heller 2008-02-13 20:21:53 +00:00
parent 91a1dec492
commit a06a1a88ee
7 changed files with 177 additions and 1 deletions

View File

@ -2011,6 +2011,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:

View File

@ -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()

View File

@ -400,6 +400,9 @@ Core and builtins
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.

View File

@ -128,6 +128,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;
@ -718,6 +721,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;
@ -1684,12 +1691,21 @@ SimpleType_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
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 @@ make_funcptrtype_dict(StgDictObject *stgdict)
"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 @@ 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!) */
@ -2255,6 +2272,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.
*/
@ -2267,6 +2323,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 },
};
@ -5107,6 +5165,10 @@ init_ctypes(void)
if (!m)
return;
_unpickle = PyObject_GetAttrString(m, "_unpickle");
if (_unpickle == NULL)
return;
if (PyType_Ready(&PyCArg_Type) < 0)
return;

View File

@ -1578,7 +1578,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},

View File

@ -286,6 +286,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 {

View File

@ -414,6 +414,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) {