library/_ctypes.py (326 lines of code) (raw):

#!/usr/bin/env python3 # Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) # $builtin-init-module$ """ctypes support module""" from builtins import _obj_as_int from weakref import ref from _builtins import ( _builtin, _bytes_check, _bytes_len, _float_check, _int_check, _memoryview_check, _memoryview_start, _mmap_check, _property, _str_check, _str_len, _tuple_check, _tuple_getitem, _tuple_len, _type, _type_check, _type_issubclass, _Unbound, _unimplemented, _weakref_check, ) _SIMPLE_TYPE_CHARS = "cbBhHiIlLdfuzZqQPXOv?g" FUNCFLAG_STDCALL = 0x0 FUNCFLAG_CDECL = 0x1 FUNCFLAG_HRESULT = 0x2 FUNCFLAG_PYTHONAPI = 0x4 FUNCFLAG_USE_ERRNO = 0x8 FUNCFLAG_USE_LASTERROR = 0x10 __version__ = "1.1.0" def _SimpleCData_value_to_type(value, typ, offset): _builtin() # Metaclasses # Note: CPython does not define _CDataType as a metaclass, but rather as a # set of methods which are added to the other metaclasses. class _CDataType(type): def from_address(cls, *args, **kwargs): _unimplemented() def from_buffer(cls, obj, offset=0): if not hasattr(cls, "_type_"): raise TypeError("abstract class") if not _type_issubclass(cls, _SimpleCData) and not _type_issubclass(cls, Array): _unimplemented() if not _memoryview_check(obj) or not _mmap_check(obj.obj): _unimplemented() if offset < 0: raise ValueError("offset cannot be negative") if obj.readonly: raise TypeError("underlying buffer is not writable") if obj.strides != (1,): raise TypeError("underlying buffer is not C contiguous") obj_len = obj.shape[0] type_size = sizeof(cls) if type_size > obj_len - offset: raise ValueError( "Buffer size too small " f"({obj_len} instead of at least {type_size + offset} bytes)" ) result = cls.__new__(cls) result._value = obj.obj result._offset = offset + _memoryview_start(obj) return result def from_buffer_copy(cls, *args, **kwargs): _unimplemented() def from_param(cls, *args, **kwargs): _unimplemented() def in_dll(cls, *args, **kwargs): _unimplemented() def __mul__(cls, length): if length < 0: raise ValueError(f"Array length must be >= 0, not {length}") if not isinstance(cls, type): raise TypeError(f"Expected a type object") key = (cls, length) result = _array_from_ctype_cache.get(key) if result is not None: return result() if _weakref_check(result) else result name = f"{cls.__name__}_Array_{length}" result = PyCArrayType(name, (Array,), {"_length_": length, "_type_": cls}) _array_from_ctype_cache[key] = ref( result, lambda _: _array_from_ctype_cache.pop(key) ) return result def _CharArray_value_to_bytes(obj, le): _builtin() def _CharArray_value_getter(self): return _CharArray_value_to_bytes(self._value, self._length_) def _CharArray_value_setter(self, value): if not _bytes_check(value): raise TypeError(f"bytes expected instead of {_type(value).__name__} instance") val_len = _bytes_len(value) if val_len > self._length_: raise ValueError("byte string too long") if _mmap_check(self._value): memoryview(self._value)[:val_len] = value else: self._value[:val_len] = value if val_len < self._length_: self._value[val_len] = 0 class PyCArrayType(_CDataType): def __new__(cls, name, bases, namespace): result = super().__new__(cls, name, bases, namespace) # The base class (Array), no checking necessary if bases[0] == _CData: return result if not hasattr(result, "_length_"): raise AttributeError("class must define a '_length_' attribute") length = result._length_ if not _int_check(length): raise TypeError("The '_length_' attribute must be an integer") if length < 0: raise ValueError("The '_length_' attribute must not be negative") if not hasattr(result, "_type_"): raise AttributeError("class must define a '_type_' attribute") result_type = result._type_ if not _type_check(result_type) or not hasattr(result_type, "_type_"): raise TypeError("_type_ must have storage info") if result_type._type_ == "c": result.value = _property(_CharArray_value_getter, _CharArray_value_setter) return result class PyCFuncPtrType(_CDataType): pass class PyCPointerType(_CDataType): def from_param(cls, *args, **kwargs): # noqa: B902 _unimplemented() def set_type(cls, *args, **kwargs): # noqa: B902 _unimplemented() class PyCSimpleType(_CDataType): def __new__(cls, name, bases, namespace): result = super().__new__(cls, name, bases, namespace) # The base class (_SimpleCData), no checking necessary if bases[0] == _CData: return result if not hasattr(result, "_type_"): raise AttributeError("class must define a '_type_' attribute") proto = result._type_ if not _str_check(proto): raise TypeError("class must define a '_type_' string attribute") if _str_len(proto) != 1: raise ValueError( "class must define a '_type_' attribute which must be a string \ of length 1" ) if proto not in _SIMPLE_TYPE_CHARS: raise AttributeError( "class must define a '_type_' attribute which must be\n" + f"a single character string containing one of '{_SIMPLE_TYPE_CHARS}'." ) # TODO(T61319024): CPython does more work here return result def from_param(cls, *args, **kwargs): # noqa: B902 _unimplemented() class PyCStructType(_CDataType): pass class UnionType(_CDataType): pass # End Metaclasses class _CData: def __ctypes_from_outparam__(self): _unimplemented() def __reduce__(self): _unimplemented() def __setstate__(self): _unimplemented() class _Pointer(_CData, metaclass=PyCPointerType): def __new__(cls, name, bases, namespace): _unimplemented() class _SimpleCData(_CData, metaclass=PyCSimpleType): def __init__(self, value=_Unbound): self._value_setter(value) self._offset = 0 def __new__(cls, *args, **kwargs): if not hasattr(cls, "_type_"): raise TypeError("abstract class") return super().__new__(cls, args, kwargs) def _value_getter(self): return _SimpleCData_value_to_type(self._value, self._type_, self._offset) def _value_setter(self, value): if self._type_ == "H" or self._type_ == "L": if _float_check(value): raise TypeError("int expected instead of float") if value is _Unbound: self._value = 0 else: self._value = _obj_as_int(value) else: _unimplemented() value = _property(_value_getter, _value_setter) class ArgumentError(Exception): pass class Array(_CData, metaclass=PyCArrayType): def __init__(self, *args, **kwargs): self._offset = 0 if _tuple_len(args) > 1: _unimplemented() elif _tuple_len(args) == 1: if self._type_._type_ != "c": _unimplemented() arg0 = _tuple_getitem(args, 0) if not _bytes_check(arg0) or _bytes_len(arg0) != 1: _unimplemented() self.value = arg0 def __len__(self): _unimplemented() def __getitem__(self): _unimplemented() def __new__(cls, *args, **kwargs): if not hasattr(cls, "_type_"): raise TypeError("abstract class") result = super().__new__(cls, args, kwargs) result._value = bytearray(bytes(cls._length_)) return result def __setitem__(self): _unimplemented() def __delitem__(self): _unimplemented() # TODO(T66886328): Delete the memset patch once __call__ is complete def _memset(addr, val, size): _builtin() class CFuncPtr(_CData, metaclass=PyCFuncPtrType): def __call__(self, *args, **kwargs): # TODO(T66886328): Delete the memset patch once __call__ is complete if self.fn_pointer == _memset_addr: # noqa: F821 if _tuple_len(args) != 3: raise TypeError("this function takes at least 3 arguments") addr = _tuple_getitem(args, 0) val = _tuple_getitem(args, 1) size = _tuple_getitem(args, 2) if not _int_check(addr) or not _int_check(val) or not _int_check(size): _unimplemented() return _memset(addr, val, size) if _tuple_len(args) != 0: _unimplemented() if hasattr(self, "callable") and self.callable is not None: _unimplemented() return _call_cfuncptr(self.fn_pointer, self.restype._type_) def __new__(cls, *args, **kwargs): if _tuple_len(args) != 1: _unimplemented() arg = args[0] if _int_check(arg): result = super().__new__(cls) result.fn_pointer = arg elif callable(arg): result = super().__new__(cls) result.callable = arg elif _tuple_check(arg): name = arg[0] if _bytes_check(name): _unimplemented() if not _str_check(name): # Note: integer argument only supported on WIN32 raise TypeError("function name must be string, bytes object or integer") dll = arg[1] handle = dll._handle if not _int_check(handle): raise TypeError( "the _handle attribute of the second argument must be an integer" ) result = super().__new__(cls) result.fn_pointer = _shared_object_symbol_address(handle, name) else: raise TypeError("argument must be callable or integer function address") result.restype = cls._restype_ return result class Structure(_CData, metaclass=PyCStructType): def __init__(self, *args, **kwargs): _unimplemented() def __new__(cls, *args, **kwargs): _unimplemented() class Union(_CData, metaclass=UnionType): def __init__(self, *args, **kwargs): _unimplemented() def __new__(cls, *args, **kwargs): _unimplemented() def _addressof(obj): _builtin() _array_from_ctype_cache = {} _pointer_type_cache = {} def _call_cfuncptr(function_ptr, result_type): _builtin() def _sizeof_typeclass(cdata_type): _builtin() def _shared_object_symbol_address(handle, name): _builtin() def addressof(obj): if not issubclass(type(obj), _CData): raise TypeError("invalid type") # TODO(T67017526): Remove mmap patch # addressof can only be called on objects which are not allocated on Pyro's # internal heap (such as an mmap). Objects on Pyro's heap do not have stable # addresses due to the relocating garbage collector, so we should not make them # visible to the user if ( not _type_issubclass(_type(obj), _SimpleCData) and not _type_issubclass(_type(obj), Array) ) or not _mmap_check(obj._value): _unimplemented() return _addressof(obj._value) def alignment(obj_or_type): _unimplemented() def byref(obj, offset=0): _unimplemented() def dlopen(name, flag): _builtin() def get_errno(): _unimplemented() def POINTER(cls): result = _pointer_type_cache.get(cls) if result is not None: return result if isinstance(cls, str): result = PyCPointerType("LP_" + cls, (_Pointer,), {}) key = id(result) elif isinstance(cls, type): result = PyCPointerType("LP_" + cls.__name__, (_Pointer,), {"_type_": cls}) key = cls else: raise TypeError("must be a ctypes type") _pointer_type_cache[key] = result return result def pointer(obj): _unimplemented() def resize(obj, size): _unimplemented() def set_errno(value): _unimplemented() def sizeof(obj_or_type): if not _type_check(obj_or_type): ty = _type(obj_or_type) else: ty = obj_or_type if not _type_issubclass(ty, _CData): raise TypeError("this type has no size") if _type_issubclass(ty, Array): return ty._length_ * sizeof(ty._type_) if _type_issubclass(ty, _SimpleCData): return _sizeof_typeclass(ty._type_) _unimplemented()