Fix some issues in ctypes (fixes #1983) (#1986)

* Support additional argument types for c_void_p parameters
* Don't allow passing bytes where a writable c_void_p is expected
* Fix type of ctypes._CDataMeta.__[r]mul__
* Rename ctypes._cparam to _CArgObject to match the real type name
* Fix value type of ctypes.HRESULT
* Add stub for ctypes.wintypes
* Fix trailing whitespace in ctypes.wintypes
* Add a workaround to ctypes.Array to fix incorrect typing in some cases
* Expand multiple assignments so mypy recognizes them as type aliases
* Rename ctypes._Pointer to pointer so it can be used externally
This commit is contained in:
dgelessus
2018-03-29 21:41:42 +02:00
committed by Michael J. Sullivan
parent f42724444f
commit 06aee52581
2 changed files with 269 additions and 41 deletions

View File

@@ -46,12 +46,11 @@ pydll: LibraryLoader[PyDLL] = ...
pythonapi: PyDLL = ...
class _CDataMeta(type):
# TODO The return type is not accurate. The method definition *should* look like this:
# def __mul__(cls: Type[_CT], other: int) -> Type[Array[_CT]]: ...
# but that is not valid, because technically a _CDataMeta might not be a Type[_CT].
# This can never actually happen, because all _CDataMeta instances are _CData subclasses, but a typechecker doesn't know that.
def __mul__(cls: _CDataMeta, other: int) -> Type[Array[_CT]]: ...
def __rmul__(cls: _CDataMeta, other: int) -> Type[Array[_CT]]: ...
# By default mypy complains about the following two methods, because strictly speaking cls
# might not be a Type[_CT]. However this can never actually happen, because the only class that
# uses _CDataMeta as its metaclass is _CData. So it's safe to ignore the errors here.
def __mul__(cls: Type[_CT], other: int) -> Type[Array[_CT]]: ... # type: ignore
def __rmul__(cls: Type[_CT], other: int) -> Type[Array[_CT]]: ... # type: ignore
class _CData(metaclass=_CDataMeta):
_b_base: int = ...
_b_needsfree_: bool = ...
@@ -63,7 +62,7 @@ class _CData(metaclass=_CDataMeta):
@classmethod
def from_address(cls: Type[_CT], address: int) -> _CT: ...
@classmethod
def from_param(cls: Type[_CT], obj: Any) -> _UnionT[_CT, _cparam]: ...
def from_param(cls: Type[_CT], obj: Any) -> _UnionT[_CT, _CArgObject]: ...
@classmethod
def in_dll(cls: Type[_CT], library: CDLL, name: str) -> _CT: ...
@@ -92,7 +91,7 @@ class _FuncPointer(_PointerLike, _CData):
@overload
def __init__(self, vtlb_index: int, name: str,
paramflags: Tuple[_PF, ...] = ...,
iid: _Pointer[c_int] = ...) -> None: ...
iid: pointer[c_int] = ...) -> None: ...
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
class ArgumentError(Exception): ...
@@ -110,13 +109,22 @@ if sys.platform == 'win32':
def PYFUNCTYPE(restype: Type[_CData],
*argtypes: Type[_CData]) -> Type[_FuncPointer]: ...
class _cparam: ...
class _CArgObject: ...
# Any type that can be implicitly converted to c_void_p when passed as a C function argument.
# (bytes is not included here, see below.)
_CVoidPLike = _UnionT[_PointerLike, Array[Any], _CArgObject, int]
# Same as above, but including types known to be read-only (i. e. bytes).
# This distinction is not strictly necessary (ctypes doesn't differentiate between const
# and non-const pointers), but it catches errors like memmove(b'foo', buf, 4)
# when memmove(buf, b'foo', 4) was intended.
_CVoidConstPLike = _UnionT[_CVoidPLike, bytes]
def addressof(obj: _CData) -> int: ...
def alignment(obj_or_type: _UnionT[_CData, Type[_CData]]) -> int: ...
def byref(obj: _CData, offset: int = ...) -> _cparam: ...
def byref(obj: _CData, offset: int = ...) -> _CArgObject: ...
_PT = TypeVar('_PT', bound=_PointerLike)
def cast(obj: _UnionT[_CData, _cparam], type: Type[_PT]) -> _PT: ...
def cast(obj: _UnionT[_CData, _CArgObject], type: Type[_PT]) -> _PT: ...
def create_string_buffer(init_or_size: _UnionT[int, bytes],
size: Optional[int] = ...) -> Array[c_char]: ...
c_buffer = create_string_buffer
@@ -130,13 +138,26 @@ if sys.platform == 'win32':
def get_errno() -> int: ...
if sys.platform == 'win32':
def get_last_error() -> int: ...
def memmove(dst: _UnionT[int, _CData],
src: _UnionT[int, _CData],
count: int) -> None: ...
def memset(dst: _UnionT[int, _CData],
c: int, count: int) -> None: ...
def POINTER(type: Type[_CT]) -> Type[_Pointer[_CT]]: ...
def pointer(obj: _CT) -> _Pointer[_CT]: ...
def memmove(dst: _CVoidPLike, src: _CVoidConstPLike, count: int) -> None: ...
def memset(dst: _CVoidPLike, c: int, count: int) -> None: ...
def POINTER(type: Type[_CT]) -> Type[pointer[_CT]]: ...
# The real ctypes.pointer is a function, not a class. The stub version of pointer behaves like
# ctypes._Pointer in that it is the base class for all pointer types. Unlike the real _Pointer,
# it can be instantiated directly (to mimic the behavior of the real pointer function).
class pointer(Generic[_CT], _PointerLike, _CData):
_type_: ClassVar[Type[_CT]] = ...
contents: _CT = ...
def __init__(self, arg: _CT = ...) -> None: ...
@overload
def __getitem__(self, i: int) -> _CT: ...
@overload
def __getitem__(self, s: slice) -> List[_CT]: ...
@overload
def __setitem__(self, i: int, o: _CT) -> None: ...
@overload
def __setitem__(self, s: slice, o: Iterable[_CT]) -> None: ...
def resize(obj: _CData, size: int) -> None: ...
if sys.version_info < (3,):
def set_conversion_mode(encoding: str, errors: str) -> Tuple[str, str]: ...
@@ -144,11 +165,11 @@ def set_errno(value: int) -> int: ...
if sys.platform == 'win32':
def set_last_error(value: int) -> int: ...
def sizeof(obj_or_type: _UnionT[_CData, Type[_CData]]) -> int: ...
def string_at(address: int, size: int = ...) -> bytes: ...
def string_at(address: _CVoidConstPLike, size: int = ...) -> bytes: ...
if sys.platform == 'win32':
def WinError(code: Optional[int] = ...,
desc: Optional[str] = ...) -> OSError: ...
def wstring_at(address: int, size: int = ...) -> str: ...
def wstring_at(address: _CVoidConstPLike, size: int = ...) -> str: ...
class _SimpleCData(Generic[_T], _CData):
value: _T = ...
@@ -202,7 +223,7 @@ class c_bool(_SimpleCData[bool]):
def __init__(self, value: bool) -> None: ...
if sys.platform == 'win32':
class HRESULT(_SimpleCData[Any]): ... # TODO undocumented
class HRESULT(_SimpleCData[int]): ... # TODO undocumented
class py_object(_SimpleCData[_T]): ...
@@ -229,27 +250,25 @@ class Array(Generic[_T], Sized, _CData):
_type_: ClassVar[Type[_T]] = ...
raw: bytes = ... # TODO only available with _T == c_char
value: bytes = ... # TODO only available with _T == c_char
def __init__(self, *args: _T) -> None: ...
# TODO These methods cannot be annotated correctly at the moment.
# All of these "Any"s stand for the array's element type, but it's not possible to use _T here,
# because of a special feature of ctypes.
# By default, when accessing an element of an Array[_T], the returned object has type _T.
# However, when _T is a "simple type" like c_int, ctypes automatically "unboxes" the object
# and converts it to the corresponding Python primitive. For example, when accessing an element
# of an Array[c_int], a Python int object is returned, not a c_int.
# This behavior does *not* apply to subclasses of "simple types".
# If MyInt is a subclass of c_int, then accessing an element of an Array[MyInt] returns
# a MyInt, not an int.
# This special behavior is not easy to model in a stub, so for now all places where
# the array element type would belong are annotated with Any instead.
def __init__(self, *args: Any) -> None: ...
@overload
def __getitem__(self, i: int) -> _T: ...
def __getitem__(self, i: int) -> Any: ...
@overload
def __getitem__(self, s: slice) -> List[_T]: ...
def __getitem__(self, s: slice) -> List[Any]: ...
@overload
def __setitem__(self, i: int, o: _T) -> None: ...
def __setitem__(self, i: int, o: Any) -> None: ...
@overload
def __setitem__(self, s: slice, o: Iterable[_T]) -> None: ...
def __iter__(self) -> Iterable[_T]: ...
class _Pointer(Generic[_T], _PointerLike, _CData):
_type_: ClassVar[Type[_T]] = ...
contents: _T = ...
def __init__(self, arg: _T = ...) -> None: ...
@overload
def __getitem__(self, i: int) -> _T: ...
@overload
def __getitem__(self, s: slice) -> List[_T]: ...
@overload
def __setitem__(self, i: int, o: _T) -> None: ...
@overload
def __setitem__(self, s: slice, o: Iterable[_T]) -> None: ...
def __setitem__(self, s: slice, o: Iterable[Any]) -> None: ...
def __iter__(self) -> Iterable[Any]: ...

View File

@@ -0,0 +1,209 @@
from ctypes import (
_SimpleCData, Array, Structure, c_byte, c_char, c_char_p, c_double, c_float, c_int, c_long,
c_longlong, c_short, c_uint, c_ulong, c_ulonglong, c_ushort, c_void_p, c_wchar, c_wchar_p,
pointer,
)
BYTE = c_byte
WORD = c_ushort
DWORD = c_ulong
CHAR = c_char
WCHAR = c_wchar
UINT = c_uint
INT = c_int
DOUBLE = c_double
FLOAT = c_float
BOOLEAN = BYTE
BOOL = c_long
class VARIANT_BOOL(_SimpleCData[bool]): ...
ULONG = c_ulong
LONG = c_long
USHORT = c_ushort
SHORT = c_short
LARGE_INTEGER = c_longlong
_LARGE_INTEGER = c_longlong
ULARGE_INTEGER = c_ulonglong
_ULARGE_INTEGER = c_ulonglong
OLESTR = c_wchar_p
LPOLESTR = c_wchar_p
LPCOLESTR = c_wchar_p
LPWSTR = c_wchar_p
LPCWSTR = c_wchar_p
LPSTR = c_char_p
LPCSTR = c_char_p
LPVOID = c_void_p
LPCVOID = c_void_p
# These two types are pointer-sized unsigned and signed ints, respectively.
# At runtime, they are either c_[u]long or c_[u]longlong, depending on the host's pointer size
# (they are not really separate classes).
class WPARAM(_SimpleCData[int]): ...
class LPARAM(_SimpleCData[int]): ...
ATOM = WORD
LANGID = WORD
COLORREF = DWORD
LGRPID = DWORD
LCTYPE = DWORD
LCID = DWORD
HANDLE = c_void_p
HACCEL = HANDLE
HBITMAP = HANDLE
HBRUSH = HANDLE
HCOLORSPACE = HANDLE
HDC = HANDLE
HDESK = HANDLE
HDWP = HANDLE
HENHMETAFILE = HANDLE
HFONT = HANDLE
HGDIOBJ = HANDLE
HGLOBAL = HANDLE
HHOOK = HANDLE
HICON = HANDLE
HINSTANCE = HANDLE
HKEY = HANDLE
HKL = HANDLE
HLOCAL = HANDLE
HMENU = HANDLE
HMETAFILE = HANDLE
HMODULE = HANDLE
HMONITOR = HANDLE
HPALETTE = HANDLE
HPEN = HANDLE
HRGN = HANDLE
HRSRC = HANDLE
HSTR = HANDLE
HTASK = HANDLE
HWINSTA = HANDLE
HWND = HANDLE
SC_HANDLE = HANDLE
SERVICE_STATUS_HANDLE = HANDLE
class RECT(Structure):
left: LONG
top: LONG
right: LONG
bottom: LONG
RECTL = RECT
_RECTL = RECT
tagRECT = RECT
class _SMALL_RECT(Structure):
Left: SHORT
Top: SHORT
Right: SHORT
Bottom: SHORT
SMALL_RECT = _SMALL_RECT
class _COORD(Structure):
X: SHORT
Y: SHORT
class POINT(Structure):
x: LONG
y: LONG
POINTL = POINT
_POINTL = POINT
tagPOINT = POINT
class SIZE(Structure):
cx: LONG
cy: LONG
SIZEL = SIZE
tagSIZE = SIZE
def RGB(red: int, green: int, blue: int) -> int: ...
class FILETIME(Structure):
dwLowDateTime: DWORD
dwHighDateTime: DWORD
_FILETIME = FILETIME
class MSG(Structure):
hWnd: HWND
message: UINT
wParam: WPARAM
lParam: LPARAM
time: DWORD
pt: POINT
tagMSG = MSG
MAX_PATH: int
class WIN32_FIND_DATAA(Structure):
dwFileAttributes: DWORD
ftCreationTime: FILETIME
ftLastAccessTime: FILETIME
ftLastWriteTime: FILETIME
nFileSizeHigh: DWORD
nFileSizeLow: DWORD
dwReserved0: DWORD
dwReserved1: DWORD
cFileName: Array[CHAR]
cAlternateFileName: Array[CHAR]
class WIN32_FIND_DATAW(Structure):
dwFileAttributes: DWORD
ftCreationTime: FILETIME
ftLastAccessTime: FILETIME
ftLastWriteTime: FILETIME
nFileSizeHigh: DWORD
nFileSizeLow: DWORD
dwReserved0: DWORD
dwReserved1: DWORD
cFileName: Array[WCHAR]
cAlternateFileName: Array[WCHAR]
# These pointer type definitions use pointer[...] instead of POINTER(...), to allow them
# to be used in type annotations.
PBOOL = pointer[BOOL]
LPBOOL = pointer[BOOL]
PBOOLEAN = pointer[BOOLEAN]
PBYTE = pointer[BYTE]
LPBYTE = pointer[BYTE]
PCHAR = pointer[CHAR]
LPCOLORREF = pointer[COLORREF]
PDWORD = pointer[DWORD]
LPDWORD = pointer[DWORD]
PFILETIME = pointer[FILETIME]
LPFILETIME = pointer[FILETIME]
PFLOAT = pointer[FLOAT]
PHANDLE = pointer[HANDLE]
LPHANDLE = pointer[HANDLE]
PHKEY = pointer[HKEY]
LPHKL = pointer[HKL]
PINT = pointer[INT]
LPINT = pointer[INT]
PLARGE_INTEGER = pointer[LARGE_INTEGER]
PLCID = pointer[LCID]
PLONG = pointer[LONG]
LPLONG = pointer[LONG]
PMSG = pointer[MSG]
LPMSG = pointer[MSG]
PPOINT = pointer[POINT]
LPPOINT = pointer[POINT]
PPOINTL = pointer[POINTL]
PRECT = pointer[RECT]
LPRECT = pointer[RECT]
PRECTL = pointer[RECTL]
LPRECTL = pointer[RECTL]
LPSC_HANDLE = pointer[SC_HANDLE]
PSHORT = pointer[SHORT]
PSIZE = pointer[SIZE]
LPSIZE = pointer[SIZE]
PSIZEL = pointer[SIZEL]
LPSIZEL = pointer[SIZEL]
PSMALL_RECT = pointer[SMALL_RECT]
PUINT = pointer[UINT]
LPUINT = pointer[UINT]
PULARGE_INTEGER = pointer[ULARGE_INTEGER]
PULONG = pointer[ULONG]
PUSHORT = pointer[USHORT]
PWCHAR = pointer[WCHAR]
PWIN32_FIND_DATAA = pointer[WIN32_FIND_DATAA]
LPWIN32_FIND_DATAA = pointer[WIN32_FIND_DATAA]
PWIN32_FIND_DATAW = pointer[WIN32_FIND_DATAW]
LPWIN32_FIND_DATAW = pointer[WIN32_FIND_DATAW]
PWORD = pointer[WORD]
LPWORD = pointer[WORD]