_ccallback.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. from . import _ccallback_c
  2. import ctypes
  3. PyCFuncPtr = ctypes.CFUNCTYPE(ctypes.c_void_p).__bases__[0]
  4. ffi = None
  5. class CData:
  6. pass
  7. def _import_cffi():
  8. global ffi, CData
  9. if ffi is not None:
  10. return
  11. try:
  12. import cffi
  13. ffi = cffi.FFI()
  14. CData = ffi.CData
  15. except ImportError:
  16. ffi = False
  17. class LowLevelCallable(tuple):
  18. """
  19. Low-level callback function.
  20. Parameters
  21. ----------
  22. function : {PyCapsule, ctypes function pointer, cffi function pointer}
  23. Low-level callback function.
  24. user_data : {PyCapsule, ctypes void pointer, cffi void pointer}
  25. User data to pass on to the callback function.
  26. signature : str, optional
  27. Signature of the function. If omitted, determined from *function*,
  28. if possible.
  29. Attributes
  30. ----------
  31. function
  32. Callback function given.
  33. user_data
  34. User data given.
  35. signature
  36. Signature of the function.
  37. Methods
  38. -------
  39. from_cython
  40. Class method for constructing callables from Cython C-exported
  41. functions.
  42. Notes
  43. -----
  44. The argument ``function`` can be one of:
  45. - PyCapsule, whose name contains the C function signature
  46. - ctypes function pointer
  47. - cffi function pointer
  48. The signature of the low-level callback must match one of those expected
  49. by the routine it is passed to.
  50. If constructing low-level functions from a PyCapsule, the name of the
  51. capsule must be the corresponding signature, in the format::
  52. return_type (arg1_type, arg2_type, ...)
  53. For example::
  54. "void (double)"
  55. "double (double, int *, void *)"
  56. The context of a PyCapsule passed in as ``function`` is used as ``user_data``,
  57. if an explicit value for ``user_data`` was not given.
  58. """
  59. # Make the class immutable
  60. __slots__ = ()
  61. def __new__(cls, function, user_data=None, signature=None):
  62. # We need to hold a reference to the function & user data,
  63. # to prevent them going out of scope
  64. item = cls._parse_callback(function, user_data, signature)
  65. return tuple.__new__(cls, (item, function, user_data))
  66. def __repr__(self):
  67. return "LowLevelCallable({!r}, {!r})".format(self.function, self.user_data)
  68. @property
  69. def function(self):
  70. return tuple.__getitem__(self, 1)
  71. @property
  72. def user_data(self):
  73. return tuple.__getitem__(self, 2)
  74. @property
  75. def signature(self):
  76. return _ccallback_c.get_capsule_signature(tuple.__getitem__(self, 0))
  77. def __getitem__(self, idx):
  78. raise ValueError()
  79. @classmethod
  80. def from_cython(cls, module, name, user_data=None, signature=None):
  81. """
  82. Create a low-level callback function from an exported Cython function.
  83. Parameters
  84. ----------
  85. module : module
  86. Cython module where the exported function resides
  87. name : str
  88. Name of the exported function
  89. user_data : {PyCapsule, ctypes void pointer, cffi void pointer}, optional
  90. User data to pass on to the callback function.
  91. signature : str, optional
  92. Signature of the function. If omitted, determined from *function*.
  93. """
  94. try:
  95. function = module.__pyx_capi__[name]
  96. except AttributeError as e:
  97. raise ValueError("Given module is not a Cython module with __pyx_capi__ attribute") from e
  98. except KeyError as e:
  99. raise ValueError("No function {!r} found in __pyx_capi__ of the module".format(name)) from e
  100. return cls(function, user_data, signature)
  101. @classmethod
  102. def _parse_callback(cls, obj, user_data=None, signature=None):
  103. _import_cffi()
  104. if isinstance(obj, LowLevelCallable):
  105. func = tuple.__getitem__(obj, 0)
  106. elif isinstance(obj, PyCFuncPtr):
  107. func, signature = _get_ctypes_func(obj, signature)
  108. elif isinstance(obj, CData):
  109. func, signature = _get_cffi_func(obj, signature)
  110. elif _ccallback_c.check_capsule(obj):
  111. func = obj
  112. else:
  113. raise ValueError("Given input is not a callable or a low-level callable (pycapsule/ctypes/cffi)")
  114. if isinstance(user_data, ctypes.c_void_p):
  115. context = _get_ctypes_data(user_data)
  116. elif isinstance(user_data, CData):
  117. context = _get_cffi_data(user_data)
  118. elif user_data is None:
  119. context = 0
  120. elif _ccallback_c.check_capsule(user_data):
  121. context = user_data
  122. else:
  123. raise ValueError("Given user data is not a valid low-level void* pointer (pycapsule/ctypes/cffi)")
  124. return _ccallback_c.get_raw_capsule(func, signature, context)
  125. #
  126. # ctypes helpers
  127. #
  128. def _get_ctypes_func(func, signature=None):
  129. # Get function pointer
  130. func_ptr = ctypes.cast(func, ctypes.c_void_p).value
  131. # Construct function signature
  132. if signature is None:
  133. signature = _typename_from_ctypes(func.restype) + " ("
  134. for j, arg in enumerate(func.argtypes):
  135. if j == 0:
  136. signature += _typename_from_ctypes(arg)
  137. else:
  138. signature += ", " + _typename_from_ctypes(arg)
  139. signature += ")"
  140. return func_ptr, signature
  141. def _typename_from_ctypes(item):
  142. if item is None:
  143. return "void"
  144. elif item is ctypes.c_void_p:
  145. return "void *"
  146. name = item.__name__
  147. pointer_level = 0
  148. while name.startswith("LP_"):
  149. pointer_level += 1
  150. name = name[3:]
  151. if name.startswith('c_'):
  152. name = name[2:]
  153. if pointer_level > 0:
  154. name += " " + "*"*pointer_level
  155. return name
  156. def _get_ctypes_data(data):
  157. # Get voidp pointer
  158. return ctypes.cast(data, ctypes.c_void_p).value
  159. #
  160. # CFFI helpers
  161. #
  162. def _get_cffi_func(func, signature=None):
  163. # Get function pointer
  164. func_ptr = ffi.cast('uintptr_t', func)
  165. # Get signature
  166. if signature is None:
  167. signature = ffi.getctype(ffi.typeof(func)).replace('(*)', ' ')
  168. return func_ptr, signature
  169. def _get_cffi_data(data):
  170. # Get pointer
  171. return ffi.cast('uintptr_t', data)