summaryrefslogtreecommitdiffstats
path: root/Lib
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 /Lib
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 'Lib')
-rw-r--r--Lib/ctypes/__init__.py72
-rw-r--r--Lib/ctypes/test/test_errno.py76
2 files changed, 125 insertions, 23 deletions
diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py
index 41d39dc..d3e11dc 100644
--- a/Lib/ctypes/__init__.py
+++ b/Lib/ctypes/__init__.py
@@ -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:
diff --git a/Lib/ctypes/test/test_errno.py b/Lib/ctypes/test/test_errno.py
new file mode 100644
index 0000000..ce1cfad
--- /dev/null
+++ b/Lib/ctypes/test/test_errno.py
@@ -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()