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()