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 /*