Thomas Heller <[EMAIL PROTECTED]> added the comment:
Here is a patch implementing the plan. This text could serve as a start for the
documentation, but it also describes the current implementation. Usage recipes
should probably be added:
/*
ctypes maintains a module-global, but thread-local, variable that contains
an error number; called 'ctypes_errno' for this discussion. This variable
is a private copy of the stdlib 'errno' value; it is swapped with the
'errno' variable on several occasions.
Foreign functions created with CDLL(..., use_errno=True), when called, swap
the values just before they are actual function call, and swapped again
afterwards. The 'use_errno' parameter defaults to False, in this case
ctypes_errno is not touched.
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 for CFUNCTYPE(..., use_errno=TRUE) or
WINFUNCTYPE(..., use_errno=True).
Two new ctypes functions are provided to access the 'ctypes_errno' value
from Python:
- ctypes.set_errno(value) sets ctypes_errno to 'value', the previous
ctypes_errno value is returned.
- ctypes.get_errno() returns the current ctypes_errno value.
The same scheme is implemented on Windows for GetLastError() and
SetLastError(), and the CDLL and WinDLL optional parameter is named
'use_LastError', and also defaults to False.
On Windows, TlsSetValue and TlsGetValue calls are used to provide thread
local storage for the variables; ctypes compiled with __GNUC__ uses __thread
variables.
*/
Added file: http://bugs.python.org/file10480/ctypes-errno-3.patch
_______________________________________
Python tracker <[EMAIL PROTECTED]>
<http://bugs.python.org/issue1798>
_______________________________________
Index: Lib/ctypes/__init__.py
===================================================================
--- Lib/ctypes/__init__.py (revision 63822)
+++ Lib/ctypes/__init__.py (working copy)
@@ -33,7 +33,9 @@
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,8 @@
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, **kw) -> function prototype.
restype: the result type
argtypes: a sequence specifying the argument types
@@ -88,14 +90,21 @@
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_LastError", 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 +115,23 @@
_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_LastError", 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 +140,7 @@
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 +330,24 @@
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_LastError=False):
self._name = name
+ flags = self._func_flags_
+ if use_errno:
+ flags |= _FUNCFLAG_USE_ERRNO
+ if use_LastError:
+ 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 +377,7 @@
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 +385,7 @@
"""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 +409,8 @@
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):
@@ -420,10 +444,7 @@
windll = LibraryLoader(WinDLL)
oledll = LibraryLoader(OleDLL)
- if _os.name == "nt":
- GetLastError = windll.kernel32.GetLastError
- else:
- GetLastError = windll.coredll.GetLastError
+ from _ctypes import GetLastError, SetLastError
def WinError(code=None, descr=None):
if code is None:
Index: Lib/ctypes/test/test_errno.py
===================================================================
--- Lib/ctypes/test/test_errno.py (revision 0)
+++ Lib/ctypes/test/test_errno.py (revision 0)
@@ -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_LastError=True)
+ GetModuleHandle = dll.GetModuleHandleW
+ GetModuleHandle.argtypes = [c_wchar_p]
+
+ self.failUnlessEqual(0, GetModuleHandle("foo"))
+ self.failUnlessEqual(GetLastError(), 126)
+
+ self.failUnlessEqual(SetLastError(32), 126)
+ self.failUnlessEqual(GetLastError(), 32)
+
+ def _worker():
+ SetLastError(0)
+
+ dll = WinDLL("kernel32", use_LastError=False)
+ GetModuleHandle = dll.GetModuleHandleW
+ GetModuleHandle.argtypes = [c_wchar_p]
+ GetModuleHandle("bar")
+
+ self.failUnlessEqual(GetLastError(), 0)
+
+ t = threading.Thread(target=_worker)
+ t.start()
+ t.join()
+
+ self.failUnlessEqual(GetLastError(), 32)
+
+ SetLastError(0)
+
+if __name__ == "__main__":
+ unittest.main()
Property changes on: Lib\ctypes\test\test_errno.py
___________________________________________________________________
Name: svn:eol-style
+ native
Index: Modules/_ctypes/_ctypes.c
===================================================================
--- Modules/_ctypes/_ctypes.c (revision 63822)
+++ Modules/_ctypes/_ctypes.c (working copy)
@@ -3271,7 +3271,7 @@
thunk = AllocFunctionCallback(callable,
dict->argtypes,
dict->restype,
- dict->flags & FUNCFLAG_CDECL);
+ dict->flags);
if (!thunk)
return NULL;
@@ -5273,6 +5273,17 @@
if (!m)
return;
+#ifdef MS_WIN32
+ dwTlsIndex_LastError = TlsAlloc();
+ dwTlsIndex_errno = TlsAlloc();
+ if (dwTlsIndex_LastError == TLS_OUT_OF_INDEXES
+ || dwTlsIndex_errno == TLS_OUT_OF_INDEXES) {
+ PyErr_SetString(PyExc_MemoryError,
+ "Could not allocate TLSIndex for LastError
value");
+ return;
+ }
+#endif
+
_pointer_type_cache = PyDict_New();
if (_pointer_type_cache == NULL)
return;
@@ -5394,6 +5405,8 @@
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");
Index: Modules/_ctypes/callbacks.c
===================================================================
--- Modules/_ctypes/callbacks.c (revision 63822)
+++ Modules/_ctypes/callbacks.c (working copy)
@@ -189,6 +189,7 @@
SETFUNC setfunc,
PyObject *callable,
PyObject *converters,
+ int flags,
void **pArgs)
{
Py_ssize_t i;
@@ -271,8 +272,22 @@
#define CHECK(what, x) \
if (x == NULL) _AddTraceback(what, "_ctypes/callbacks.c", __LINE__ - 1),
PyErr_Print()
+ if (flags & FUNCFLAG_USE_ERRNO)
+ _swap_errno();
+#ifdef MS_WIN32
+ if (flags & FUNCFLAG_USE_LASTERROR)
+ _swap_last_error();
+#endif
+
result = PyObject_CallObject(callable, arglist);
CHECK("'calling callback function'", result);
+
+#ifdef MS_WIN32
+ if (flags & FUNCFLAG_USE_LASTERROR)
+ _swap_last_error();
+#endif
+ if (flags & FUNCFLAG_USE_ERRNO)
+ _swap_errno();
if ((restype != &ffi_type_void) && result) {
PyObject *keep;
assert(setfunc);
@@ -322,6 +337,7 @@
p->setfunc,
p->callable,
p->converters,
+ p->flags,
args);
}
@@ -351,7 +367,7 @@
CThunkObject *AllocFunctionCallback(PyObject *callable,
PyObject *converters,
PyObject *restype,
- int is_cdecl)
+ int flags)
{
int result;
CThunkObject *p;
@@ -371,6 +387,7 @@
goto error;
}
+ p->flags = flags;
for (i = 0; i < nArgs; ++i) {
PyObject *cnv = PySequence_GetItem(converters, i);
if (cnv == NULL)
@@ -398,7 +415,7 @@
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,
Index: Modules/_ctypes/callproc.c
===================================================================
--- Modules/_ctypes/callproc.c (revision 63822)
+++ Modules/_ctypes/callproc.c (working copy)
@@ -83,6 +83,124 @@
#define DONT_USE_SEH
#endif
+/*
+ ctypes maintains a module-global, but thread-local, variable that contains
+ an error number; called 'ctypes_errno' for this discussion. This variable
+ is a private copy of the stdlib 'errno' value; it is swapped with the
+ 'errno' variable on several occasions.
+
+ Foreign functions created with CDLL(..., use_errno=True), when called, swap
+ the values just before they are actual function call, and swapped again
+ afterwards. The 'use_errno' parameter defaults to False, in this case
+ ctypes_errno is not touched.
+
+ 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 for CFUNCTYPE(..., use_errno=TRUE) or
+ WINFUNCTYPE(..., use_errno=True).
+
+ Two new ctypes functions are provided to access the 'ctypes_errno' value
+ from Python:
+
+ - ctypes.set_errno(value) sets ctypes_errno to 'value', the previous
+ ctypes_errno value is returned.
+
+ - ctypes.get_errno() returns the current ctypes_errno value.
+
+ The same scheme is implemented on Windows for GetLastError() and
+ SetLastError(), and the CDLL and WinDLL optional parameter is named
+ 'use_LastError', and also defaults to False.
+
+ On Windows, TlsSetValue and TlsGetValue calls are used to provide thread
+ local storage for the variables; ctypes compiled with __GNUC__ uses __thread
+ variables.
+*/
+
+#if defined(MS_WIN32)
+DWORD dwTlsIndex_LastError;
+DWORD dwTlsIndex_errno;
+
+void
+_swap_last_error(void)
+{
+ DWORD temp = GetLastError();
+ SetLastError((DWORD)TlsGetValue(dwTlsIndex_LastError));
+ TlsSetValue(dwTlsIndex_LastError, (void *)temp);
+}
+
+static PyObject *
+get_LastError(PyObject *self, PyObject *args)
+{
+ return PyInt_FromLong((DWORD)TlsGetValue(dwTlsIndex_LastError));
+}
+
+static PyObject *
+set_LastError(PyObject *self, PyObject *args)
+{
+ DWORD new_value, prev_value;
+ if (!PyArg_ParseTuple(args, "i", &new_value))
+ return NULL;
+ prev_value = (DWORD)TlsGetValue(dwTlsIndex_LastError);
+ TlsSetValue(dwTlsIndex_LastError, (void *)new_value);
+ return PyInt_FromLong(prev_value);
+}
+
+void
+_swap_errno(void)
+{
+ int temp = errno;
+ errno = (int)TlsGetValue(dwTlsIndex_errno);
+ TlsSetValue(dwTlsIndex_errno, (void *)temp);
+}
+
+static PyObject *
+get_errno(PyObject *self, PyObject *args)
+{
+ return PyInt_FromLong((int)TlsGetValue(dwTlsIndex_errno));
+}
+
+static PyObject *
+set_errno(PyObject *self, PyObject *args)
+{
+ int new_value, prev_value;
+ if (!PyArg_ParseTuple(args, "i", &new_value))
+ return NULL;
+ prev_value = ctypes_errno;
+ ctypes_errno = new_value;
+ return PyInt_FromLong(prev_value);
+}
+
+#elif defined(__GNUC__)
+static __thread int ctypes_errno;
+
+void
+_swap_errno(void)
+{
+ int temp = errno;
+ errno = ctypes_errno;
+ ctypes_errno = temp;
+}
+
+static PyObject *
+get_errno(PyObject *self, PyObject *args)
+{
+ return PyInt_FromLong(ctypes_errno);
+}
+
+static PyObject *
+set_errno(PyObject *self, PyObject *args)
+{
+ int new_errno;
+ if (!PyArg_ParseTuple(args, "i", &new_errno))
+ return NULL;
+ return PyInt_FromLong(_save_errno(new_errno));
+}
+#else
+
+#error "TLS not implemented in this configuration"
+
+#endif
+
#ifdef MS_WIN32
PyObject *ComError;
@@ -660,7 +778,11 @@
if ((flags & FUNCFLAG_PYTHONAPI) == 0)
Py_UNBLOCK_THREADS
#endif
+ if (flags & FUNCFLAG_USE_ERRNO)
+ _swap_errno();
#ifdef MS_WIN32
+ if (flags & FUNCFLAG_USE_LASTERROR)
+ _swap_last_error();
#ifndef DONT_USE_SEH
__try {
#endif
@@ -675,7 +797,11 @@
;
}
#endif
+ if (flags & FUNCFLAG_USE_LASTERROR)
+ _swap_last_error();
#endif
+ if (flags & FUNCFLAG_USE_ERRNO)
+ _swap_errno();
#ifdef WITH_THREAD
if ((flags & FUNCFLAG_PYTHONAPI) == 0)
Py_BLOCK_THREADS
@@ -1667,6 +1793,8 @@
}
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 },
@@ -1675,6 +1803,8 @@
{"set_conversion_mode", set_conversion_mode, METH_VARARGS,
set_conversion_mode_doc},
#endif
#ifdef MS_WIN32
+ {"GetLastError", get_LastError, METH_NOARGS},
+ {"SetLastError", set_LastError, 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},
Index: Modules/_ctypes/ctypes.h
===================================================================
--- Modules/_ctypes/ctypes.h (revision 63822)
+++ Modules/_ctypes/ctypes.h (working copy)
@@ -87,6 +87,7 @@
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 CThunkObject *AllocFunctionCallback(PyObject *callable,
PyObject *converters,
PyObject *restype,
- int stdcall);
+ int flags);
/* a table entry describing a predefined ctypes type */
struct fielddesc {
char code;
@@ -303,6 +304,8 @@
#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
@@ -421,8 +424,16 @@
extern PyObject *_pointer_type_cache;
+extern void _swap_errno(void);
+
#ifdef MS_WIN32
+
+extern void _swap_last_error(void);
+
extern PyObject *ComError;
+
+extern DWORD dwTlsIndex_LastError;
+extern DWORD dwTlsIndex_errno;
#endif
/*
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com