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.
This commit is contained in:
Thomas Heller 2008-06-06 08:33:46 +00:00
parent d77554fe8c
commit fbb9c0bf3c
7 changed files with 330 additions and 27 deletions

View File

@ -33,7 +33,9 @@ if _os.name == "posix" and _sys.platform == "darwin":
DEFAULT_MODE = RTLD_GLOBAL
from _ctypes import FUNCFLAG_CDECL as _FUNCFLAG_CDECL, \
FUNCFLAG_PYTHONAPI as _FUNCFLAG_PYTHONAPI
FUNCFLAG_PYTHONAPI as _FUNCFLAG_PYTHONAPI, \
FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, \
FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR
"""
WINOLEAPI -> HRESULT
@ -73,8 +75,9 @@ def c_buffer(init, size=None):
return create_string_buffer(init, size)
_c_functype_cache = {}
def CFUNCTYPE(restype, *argtypes):
"""CFUNCTYPE(restype, *argtypes) -> function prototype.
def CFUNCTYPE(restype, *argtypes, **kw):
"""CFUNCTYPE(restype, *argtypes,
use_errno=False, use_last_error=False) -> function prototype.
restype: the result type
argtypes: a sequence specifying the argument types
@ -88,14 +91,21 @@ def CFUNCTYPE(restype, *argtypes):
prototype((ordinal number, dll object)[, paramflags]) -> foreign function exported by ordinal
prototype((function name, dll object)[, paramflags]) -> foreign function exported by name
"""
flags = _FUNCFLAG_CDECL
if kw.pop("use_errno", False):
flags |= _FUNCFLAG_USE_ERRNO
if kw.pop("use_last_error", False):
flags |= _FUNCFLAG_USE_LASTERROR
if kw:
raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
try:
return _c_functype_cache[(restype, argtypes)]
return _c_functype_cache[(restype, argtypes, flags)]
except KeyError:
class CFunctionType(_CFuncPtr):
_argtypes_ = argtypes
_restype_ = restype
_flags_ = _FUNCFLAG_CDECL
_c_functype_cache[(restype, argtypes)] = CFunctionType
_flags_ = flags
_c_functype_cache[(restype, argtypes, flags)] = CFunctionType
return CFunctionType
if _os.name in ("nt", "ce"):
@ -106,16 +116,23 @@ if _os.name in ("nt", "ce"):
_FUNCFLAG_STDCALL = _FUNCFLAG_CDECL
_win_functype_cache = {}
def WINFUNCTYPE(restype, *argtypes):
def WINFUNCTYPE(restype, *argtypes, **kw):
# docstring set later (very similar to CFUNCTYPE.__doc__)
flags = _FUNCFLAG_STDCALL
if kw.pop("use_errno", False):
flags |= _FUNCFLAG_USE_ERRNO
if kw.pop("use_last_error", False):
flags |= _FUNCFLAG_USE_LASTERROR
if kw:
raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
try:
return _win_functype_cache[(restype, argtypes)]
return _win_functype_cache[(restype, argtypes, flags)]
except KeyError:
class WinFunctionType(_CFuncPtr):
_argtypes_ = argtypes
_restype_ = restype
_flags_ = _FUNCFLAG_STDCALL
_win_functype_cache[(restype, argtypes)] = WinFunctionType
_flags_ = flags
_win_functype_cache[(restype, argtypes, flags)] = WinFunctionType
return WinFunctionType
if WINFUNCTYPE.__doc__:
WINFUNCTYPE.__doc__ = CFUNCTYPE.__doc__.replace("CFUNCTYPE", "WINFUNCTYPE")
@ -124,6 +141,7 @@ elif _os.name == "posix":
from _ctypes import dlopen as _dlopen
from _ctypes import sizeof, byref, addressof, alignment, resize
from _ctypes import get_errno, set_errno
from _ctypes import _SimpleCData
def _check_size(typ, typecode=None):
@ -313,12 +331,24 @@ class CDLL(object):
Calling the functions releases the Python GIL during the call and
reacquires it afterwards.
"""
class _FuncPtr(_CFuncPtr):
_flags_ = _FUNCFLAG_CDECL
_restype_ = c_int # default, can be overridden in instances
_func_flags_ = _FUNCFLAG_CDECL
_func_restype_ = c_int
def __init__(self, name, mode=DEFAULT_MODE, handle=None):
def __init__(self, name, mode=DEFAULT_MODE, handle=None,
use_errno=False,
use_last_error=False):
self._name = name
flags = self._func_flags_
if use_errno:
flags |= _FUNCFLAG_USE_ERRNO
if use_last_error:
flags |= _FUNCFLAG_USE_LASTERROR
class _FuncPtr(_CFuncPtr):
_flags_ = flags
_restype_ = self._func_restype_
self._FuncPtr = _FuncPtr
if handle is None:
self._handle = _dlopen(self._name, mode)
else:
@ -348,9 +378,7 @@ class PyDLL(CDLL):
access Python API functions. The GIL is not released, and
Python exceptions are handled correctly.
"""
class _FuncPtr(_CFuncPtr):
_flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI
_restype_ = c_int # default, can be overridden in instances
_func_flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI
if _os.name in ("nt", "ce"):
@ -358,9 +386,7 @@ if _os.name in ("nt", "ce"):
"""This class represents a dll exporting functions using the
Windows stdcall calling convention.
"""
class _FuncPtr(_CFuncPtr):
_flags_ = _FUNCFLAG_STDCALL
_restype_ = c_int # default, can be overridden in instances
_func_flags_ = _FUNCFLAG_STDCALL
# XXX Hm, what about HRESULT as normal parameter?
# Mustn't it derive from c_long then?
@ -384,9 +410,8 @@ if _os.name in ("nt", "ce"):
HRESULT error values are automatically raised as WindowsError
exceptions.
"""
class _FuncPtr(_CFuncPtr):
_flags_ = _FUNCFLAG_STDCALL
_restype_ = HRESULT
_func_flags_ = _FUNCFLAG_STDCALL
_func_restype_ = HRESULT
class LibraryLoader(object):
def __init__(self, dlltype):
@ -424,6 +449,7 @@ if _os.name in ("nt", "ce"):
GetLastError = windll.kernel32.GetLastError
else:
GetLastError = windll.coredll.GetLastError
from _ctypes import get_last_error, set_last_error
def WinError(code=None, descr=None):
if code is None:

View File

@ -0,0 +1,76 @@
import unittest, os, errno
from ctypes import *
from ctypes.util import find_library
import threading
class Test(unittest.TestCase):
def test_open(self):
libc_name = find_library("c")
if libc_name is not None:
libc = CDLL(libc_name, use_errno=True)
if os.name == "nt":
libc_open = libc._open
else:
libc_open = libc.open
libc_open.argtypes = c_char_p, c_int
self.failUnlessEqual(libc_open("", 0), -1)
self.failUnlessEqual(get_errno(), errno.ENOENT)
self.failUnlessEqual(set_errno(32), errno.ENOENT)
self.failUnlessEqual(get_errno(), 32)
def _worker():
set_errno(0)
libc = CDLL(libc_name, use_errno=False)
if os.name == "nt":
libc_open = libc._open
else:
libc_open = libc.open
libc_open.argtypes = c_char_p, c_int
self.failUnlessEqual(libc_open("", 0), -1)
self.failUnlessEqual(get_errno(), 0)
t = threading.Thread(target=_worker)
t.start()
t.join()
self.failUnlessEqual(get_errno(), 32)
set_errno(0)
if os.name == "nt":
def test_GetLastError(self):
dll = WinDLL("kernel32", use_last_error=True)
GetModuleHandle = dll.GetModuleHandleA
GetModuleHandle.argtypes = [c_wchar_p]
self.failUnlessEqual(0, GetModuleHandle("foo"))
self.failUnlessEqual(get_last_error(), 126)
self.failUnlessEqual(set_last_error(32), 126)
self.failUnlessEqual(get_last_error(), 32)
def _worker():
set_last_error(0)
dll = WinDLL("kernel32", use_last_error=False)
GetModuleHandle = dll.GetModuleHandleW
GetModuleHandle.argtypes = [c_wchar_p]
GetModuleHandle("bar")
self.failUnlessEqual(get_last_error(), 0)
t = threading.Thread(target=_worker)
t.start()
t.join()
self.failUnlessEqual(get_last_error(), 32)
set_last_error(0)
if __name__ == "__main__":
unittest.main()

View File

@ -72,6 +72,9 @@ Extension Modules
Library
-------
- Issue #1798: Add ctypes calling convention that allows safe access
to errno.
- Issue #2404: ctypes objects support the new pep3118 buffer interface
- Patch #2125: Add GetInteger and GetString methods for

View File

@ -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");

View File

@ -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,

View File

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

View File

@ -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;