summaryrefslogtreecommitdiffstats
path: root/Modules
diff options
context:
space:
mode:
authorThomas Heller <theller@ctypes.org>2008-06-06 08:33:46 (GMT)
committerThomas Heller <theller@ctypes.org>2008-06-06 08:33:46 (GMT)
commitfbb9c0bf3c029caf02e531dac6df80b88f01b0a0 (patch)
treeca253013237012275d57d1cb24801ceea633cbf8 /Modules
parentd77554fe8c788f8ef74581c18788cd92867e95c1 (diff)
downloadcpython-fbb9c0bf3c029caf02e531dac6df80b88f01b0a0.zip
cpython-fbb9c0bf3c029caf02e531dac6df80b88f01b0a0.tar.gz
cpython-fbb9c0bf3c029caf02e531dac6df80b88f01b0a0.tar.bz2
Issue #1798: Add ctypes calling convention that allows safe access of errno.
ctypes maintains thread-local storage that has space for two error numbers: private copies of the system 'errno' value and, on Windows, the system error code accessed by the GetLastError() and SetLastError() api functions. Foreign functions created with CDLL(..., use_errno=True), when called, swap the system 'errno' value with the private copy just before the actual function call, and swapped again immediately afterwards. The 'use_errno' parameter defaults to False, in this case 'ctypes_errno' is not touched. On Windows, foreign functions created with CDLL(..., use_last_error=True) or WinDLL(..., use_last_error=True) swap the system LastError value with the ctypes private copy. The values are also swapped immeditately before and after ctypes callback functions are called, if the callbacks are constructed using the new optional use_errno parameter set to True: CFUNCTYPE(..., use_errno=TRUE) or WINFUNCTYPE(..., use_errno=True). New ctypes functions are provided to access the ctypes private copies from Python: - ctypes.set_errno(value) and ctypes.set_last_error(value) store 'value' in the private copy and returns the previous value. - ctypes.get_errno() and ctypes.get_last_error() returns the current ctypes private copies value.
Diffstat (limited to 'Modules')
-rw-r--r--Modules/_ctypes/_ctypes.c4
-rw-r--r--Modules/_ctypes/callbacks.c42
-rw-r--r--Modules/_ctypes/callproc.c154
-rw-r--r--Modules/_ctypes/ctypes.h6
4 files changed, 202 insertions, 4 deletions
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index 30e981a..34ec9f2 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -3412,7 +3412,7 @@ CFuncPtr_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
thunk = AllocFunctionCallback(callable,
dict->argtypes,
dict->restype,
- dict->flags & FUNCFLAG_CDECL);
+ dict->flags);
if (!thunk)
return NULL;
@@ -5535,6 +5535,8 @@ init_ctypes(void)
PyModule_AddObject(m, "FUNCFLAG_STDCALL", PyInt_FromLong(FUNCFLAG_STDCALL));
#endif
PyModule_AddObject(m, "FUNCFLAG_CDECL", PyInt_FromLong(FUNCFLAG_CDECL));
+ PyModule_AddObject(m, "FUNCFLAG_USE_ERRNO", PyInt_FromLong(FUNCFLAG_USE_ERRNO));
+ PyModule_AddObject(m, "FUNCFLAG_USE_LASTERROR", PyInt_FromLong(FUNCFLAG_USE_LASTERROR));
PyModule_AddObject(m, "FUNCFLAG_PYTHONAPI", PyInt_FromLong(FUNCFLAG_PYTHONAPI));
PyModule_AddStringConstant(m, "__version__", "1.1.0");
diff --git a/Modules/_ctypes/callbacks.c b/Modules/_ctypes/callbacks.c
index b78c528..aa02154 100644
--- a/Modules/_ctypes/callbacks.c
+++ b/Modules/_ctypes/callbacks.c
@@ -189,12 +189,15 @@ static void _CallPythonObject(void *mem,
SETFUNC setfunc,
PyObject *callable,
PyObject *converters,
+ int flags,
void **pArgs)
{
Py_ssize_t i;
PyObject *result;
PyObject *arglist = NULL;
Py_ssize_t nArgs;
+ PyObject *error_object = NULL;
+ int *space;
#ifdef WITH_THREAD
PyGILState_STATE state = PyGILState_Ensure();
#endif
@@ -271,8 +274,41 @@ static void _CallPythonObject(void *mem,
#define CHECK(what, x) \
if (x == NULL) _AddTraceback(what, "_ctypes/callbacks.c", __LINE__ - 1), PyErr_Print()
+ if (flags & (FUNCFLAG_USE_ERRNO | FUNCFLAG_USE_LASTERROR)) {
+ error_object = get_error_object(&space);
+ if (error_object == NULL)
+ goto Done;
+ if (flags & FUNCFLAG_USE_ERRNO) {
+ int temp = space[0];
+ space[0] = errno;
+ errno = temp;
+ }
+#ifdef MS_WIN32
+ if (flags & FUNCFLAG_USE_LASTERROR) {
+ int temp = space[1];
+ space[1] = GetLastError();
+ SetLastError(temp);
+ }
+#endif
+ }
+
result = PyObject_CallObject(callable, arglist);
CHECK("'calling callback function'", result);
+
+#ifdef MS_WIN32
+ if (flags & FUNCFLAG_USE_LASTERROR) {
+ int temp = space[1];
+ space[1] = GetLastError();
+ SetLastError(temp);
+ }
+#endif
+ if (flags & FUNCFLAG_USE_ERRNO) {
+ int temp = space[0];
+ space[0] = errno;
+ errno = temp;
+ }
+ Py_XDECREF(error_object);
+
if ((restype != &ffi_type_void) && result) {
PyObject *keep;
assert(setfunc);
@@ -322,6 +358,7 @@ static void closure_fcn(ffi_cif *cif,
p->setfunc,
p->callable,
p->converters,
+ p->flags,
args);
}
@@ -351,7 +388,7 @@ static CThunkObject* CThunkObject_new(Py_ssize_t nArgs)
CThunkObject *AllocFunctionCallback(PyObject *callable,
PyObject *converters,
PyObject *restype,
- int is_cdecl)
+ int flags)
{
int result;
CThunkObject *p;
@@ -371,6 +408,7 @@ CThunkObject *AllocFunctionCallback(PyObject *callable,
goto error;
}
+ p->flags = flags;
for (i = 0; i < nArgs; ++i) {
PyObject *cnv = PySequence_GetItem(converters, i);
if (cnv == NULL)
@@ -398,7 +436,7 @@ CThunkObject *AllocFunctionCallback(PyObject *callable,
cc = FFI_DEFAULT_ABI;
#if defined(MS_WIN32) && !defined(_WIN32_WCE) && !defined(MS_WIN64)
- if (is_cdecl == 0)
+ if ((flags & FUNCFLAG_CDECL) == 0)
cc = FFI_STDCALL;
#endif
result = ffi_prep_cif(&p->cif, cc,
diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c
index 0520176..89a4d68 100644
--- a/Modules/_ctypes/callproc.c
+++ b/Modules/_ctypes/callproc.c
@@ -83,7 +83,129 @@
#define DONT_USE_SEH
#endif
+/*
+ ctypes maintains thread-local storage that has space for two error numbers:
+ private copies of the system 'errno' value and, on Windows, the system error code
+ accessed by the GetLastError() and SetLastError() api functions.
+
+ Foreign functions created with CDLL(..., use_errno=True), when called, swap
+ the system 'errno' value with the private copy just before the actual
+ function call, and swapped again immediately afterwards. The 'use_errno'
+ parameter defaults to False, in this case 'ctypes_errno' is not touched.
+
+ On Windows, foreign functions created with CDLL(..., use_last_error=True) or
+ WinDLL(..., use_last_error=True) swap the system LastError value with the
+ ctypes private copy.
+
+ The values are also swapped immeditately before and after ctypes callback
+ functions are called, if the callbacks are constructed using the new
+ optional use_errno parameter set to True: CFUNCTYPE(..., use_errno=TRUE) or
+ WINFUNCTYPE(..., use_errno=True).
+
+ New ctypes functions are provided to access the ctypes private copies from
+ Python:
+
+ - ctypes.set_errno(value) and ctypes.set_last_error(value) store 'value' in
+ the private copy and returns the previous value.
+
+ - ctypes.get_errno() and ctypes.get_last_error() returns the current ctypes
+ private copies value.
+*/
+
+/*
+ This function creates and returns a thread-local Python object that has
+ space to store two integer error numbers; once created the Python object is
+ kept alive in the thread state dictionary as long as the thread itself.
+*/
+PyObject *
+get_error_object(int **pspace)
+{
+ PyObject *dict = PyThreadState_GetDict();
+ PyObject *errobj;
+ if (dict == 0) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "cannot get thread state");
+ return NULL;
+ }
+ errobj = PyDict_GetItemString(dict, "ctypes.error_object");
+ if (errobj)
+ Py_INCREF(errobj);
+ else {
+ void *space = PyMem_Malloc(sizeof(int) * 2);
+ if (space == NULL)
+ return NULL;
+ memset(space, 0, sizeof(int) * 2);
+ errobj = PyCObject_FromVoidPtr(space, PyMem_Free);
+ if (errobj == NULL)
+ return NULL;
+ if (-1 == PyDict_SetItemString(dict, "ctypes.error_object",
+ errobj)) {
+ Py_DECREF(errobj);
+ return NULL;
+ }
+ }
+ *pspace = (int *)PyCObject_AsVoidPtr(errobj);
+ return errobj;
+}
+
+static PyObject *
+get_error_internal(PyObject *self, PyObject *args, int index)
+{
+ int *space;
+ PyObject *errobj = get_error_object(&space);
+ PyObject *result;
+
+ if (errobj == NULL)
+ return NULL;
+ result = PyInt_FromLong(space[index]);
+ Py_DECREF(errobj);
+ return result;
+}
+
+static PyObject *
+set_error_internal(PyObject *self, PyObject *args, int index)
+{
+ int new_errno, old_errno;
+ PyObject *errobj;
+ int *space;
+
+ if (!PyArg_ParseTuple(args, "i", &new_errno))
+ return NULL;
+ errobj = get_error_object(&space);
+ if (errobj == NULL)
+ return NULL;
+ old_errno = space[index];
+ space[index] = new_errno;
+ Py_DECREF(errobj);
+ return PyInt_FromLong(old_errno);
+}
+
+static PyObject *
+get_errno(PyObject *self, PyObject *args)
+{
+ return get_error_internal(self, args, 0);
+}
+
+static PyObject *
+set_errno(PyObject *self, PyObject *args)
+{
+ return set_error_internal(self, args, 0);
+}
+
#ifdef MS_WIN32
+
+static PyObject *
+get_last_error(PyObject *self, PyObject *args)
+{
+ return get_error_internal(self, args, 1);
+}
+
+static PyObject *
+set_last_error(PyObject *self, PyObject *args)
+{
+ return set_error_internal(self, args, 1);
+}
+
PyObject *ComError;
static TCHAR *FormatError(DWORD code)
@@ -625,6 +747,8 @@ static int _call_function_pointer(int flags,
#ifdef WITH_THREAD
PyThreadState *_save = NULL; /* For Py_BLOCK_THREADS and Py_UNBLOCK_THREADS */
#endif
+ PyObject *error_object = NULL;
+ int *space;
ffi_cif cif;
int cc;
#ifdef MS_WIN32
@@ -656,11 +780,26 @@ static int _call_function_pointer(int flags,
return -1;
}
+ if (flags & (FUNCFLAG_USE_ERRNO | FUNCFLAG_USE_LASTERROR)) {
+ error_object = get_error_object(&space);
+ if (error_object == NULL)
+ return -1;
+ }
#ifdef WITH_THREAD
if ((flags & FUNCFLAG_PYTHONAPI) == 0)
Py_UNBLOCK_THREADS
#endif
+ if (flags & FUNCFLAG_USE_ERRNO) {
+ int temp = space[0];
+ space[0] = errno;
+ errno = temp;
+ }
#ifdef MS_WIN32
+ if (flags & FUNCFLAG_USE_LASTERROR) {
+ int temp = space[1];
+ space[1] = GetLastError();
+ SetLastError(temp);
+ }
#ifndef DONT_USE_SEH
__try {
#endif
@@ -675,7 +814,18 @@ static int _call_function_pointer(int flags,
;
}
#endif
+ if (flags & FUNCFLAG_USE_LASTERROR) {
+ int temp = space[1];
+ space[1] = GetLastError();
+ SetLastError(temp);
+ }
#endif
+ if (flags & FUNCFLAG_USE_ERRNO) {
+ int temp = space[0];
+ space[0] = errno;
+ errno = temp;
+ }
+ Py_XDECREF(error_object);
#ifdef WITH_THREAD
if ((flags & FUNCFLAG_PYTHONAPI) == 0)
Py_BLOCK_THREADS
@@ -1692,6 +1842,8 @@ buffer_info(PyObject *self, PyObject *arg)
}
PyMethodDef module_methods[] = {
+ {"get_errno", get_errno, METH_NOARGS},
+ {"set_errno", set_errno, METH_VARARGS},
{"POINTER", POINTER, METH_O },
{"pointer", pointer, METH_O },
{"_unpickle", unpickle, METH_VARARGS },
@@ -1702,6 +1854,8 @@ PyMethodDef module_methods[] = {
{"set_conversion_mode", set_conversion_mode, METH_VARARGS, set_conversion_mode_doc},
#endif
#ifdef MS_WIN32
+ {"get_last_error", get_last_error, METH_NOARGS},
+ {"set_last_error", set_last_error, METH_VARARGS},
{"CopyComPointer", copy_com_pointer, METH_VARARGS, copy_com_pointer_doc},
{"FormatError", format_error, METH_VARARGS, format_error_doc},
{"LoadLibrary", load_library, METH_VARARGS, load_library_doc},
diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h
index d068ea5..96db12f 100644
--- a/Modules/_ctypes/ctypes.h
+++ b/Modules/_ctypes/ctypes.h
@@ -87,6 +87,7 @@ typedef struct {
PyObject_VAR_HEAD
ffi_closure *pcl; /* the C callable */
ffi_cif cif;
+ int flags;
PyObject *converters;
PyObject *callable;
PyObject *restype;
@@ -185,7 +186,7 @@ extern PyMethodDef module_methods[];
extern CThunkObject *AllocFunctionCallback(PyObject *callable,
PyObject *converters,
PyObject *restype,
- int stdcall);
+ int flags);
/* a table entry describing a predefined ctypes type */
struct fielddesc {
char code;
@@ -311,6 +312,8 @@ PyObject *_CallProc(PPROC pProc,
#define FUNCFLAG_CDECL 0x1
#define FUNCFLAG_HRESULT 0x2
#define FUNCFLAG_PYTHONAPI 0x4
+#define FUNCFLAG_USE_ERRNO 0x8
+#define FUNCFLAG_USE_LASTERROR 0x10
#define TYPEFLAG_ISPOINTER 0x100
#define TYPEFLAG_HASPOINTER 0x200
@@ -429,6 +432,7 @@ extern char *alloc_format_string(const char *prefix, const char *suffix);
extern int IsSimpleSubType(PyObject *obj);
extern PyObject *_pointer_type_cache;
+PyObject *get_error_object(int **pspace);
#ifdef MS_WIN32
extern PyObject *ComError;