ctypeslib.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. """
  2. ============================
  3. ``ctypes`` Utility Functions
  4. ============================
  5. See Also
  6. --------
  7. load_library : Load a C library.
  8. ndpointer : Array restype/argtype with verification.
  9. as_ctypes : Create a ctypes array from an ndarray.
  10. as_array : Create an ndarray from a ctypes array.
  11. References
  12. ----------
  13. .. [1] "SciPy Cookbook: ctypes", https://scipy-cookbook.readthedocs.io/items/Ctypes.html
  14. Examples
  15. --------
  16. Load the C library:
  17. >>> _lib = np.ctypeslib.load_library('libmystuff', '.') #doctest: +SKIP
  18. Our result type, an ndarray that must be of type double, be 1-dimensional
  19. and is C-contiguous in memory:
  20. >>> array_1d_double = np.ctypeslib.ndpointer(
  21. ... dtype=np.double,
  22. ... ndim=1, flags='CONTIGUOUS') #doctest: +SKIP
  23. Our C-function typically takes an array and updates its values
  24. in-place. For example::
  25. void foo_func(double* x, int length)
  26. {
  27. int i;
  28. for (i = 0; i < length; i++) {
  29. x[i] = i*i;
  30. }
  31. }
  32. We wrap it using:
  33. >>> _lib.foo_func.restype = None #doctest: +SKIP
  34. >>> _lib.foo_func.argtypes = [array_1d_double, c_int] #doctest: +SKIP
  35. Then, we're ready to call ``foo_func``:
  36. >>> out = np.empty(15, dtype=np.double)
  37. >>> _lib.foo_func(out, len(out)) #doctest: +SKIP
  38. """
  39. __all__ = ['load_library', 'ndpointer', 'c_intp', 'as_ctypes', 'as_array',
  40. 'as_ctypes_type']
  41. import os
  42. from numpy import (
  43. integer, ndarray, dtype as _dtype, asarray, frombuffer
  44. )
  45. from numpy.core.multiarray import _flagdict, flagsobj
  46. try:
  47. import ctypes
  48. except ImportError:
  49. ctypes = None
  50. if ctypes is None:
  51. def _dummy(*args, **kwds):
  52. """
  53. Dummy object that raises an ImportError if ctypes is not available.
  54. Raises
  55. ------
  56. ImportError
  57. If ctypes is not available.
  58. """
  59. raise ImportError("ctypes is not available.")
  60. load_library = _dummy
  61. as_ctypes = _dummy
  62. as_array = _dummy
  63. from numpy import intp as c_intp
  64. _ndptr_base = object
  65. else:
  66. import numpy.core._internal as nic
  67. c_intp = nic._getintp_ctype()
  68. del nic
  69. _ndptr_base = ctypes.c_void_p
  70. # Adapted from Albert Strasheim
  71. def load_library(libname, loader_path):
  72. """
  73. It is possible to load a library using
  74. >>> lib = ctypes.cdll[<full_path_name>] # doctest: +SKIP
  75. But there are cross-platform considerations, such as library file extensions,
  76. plus the fact Windows will just load the first library it finds with that name.
  77. NumPy supplies the load_library function as a convenience.
  78. .. versionchanged:: 1.20.0
  79. Allow libname and loader_path to take any
  80. :term:`python:path-like object`.
  81. Parameters
  82. ----------
  83. libname : path-like
  84. Name of the library, which can have 'lib' as a prefix,
  85. but without an extension.
  86. loader_path : path-like
  87. Where the library can be found.
  88. Returns
  89. -------
  90. ctypes.cdll[libpath] : library object
  91. A ctypes library object
  92. Raises
  93. ------
  94. OSError
  95. If there is no library with the expected extension, or the
  96. library is defective and cannot be loaded.
  97. """
  98. if ctypes.__version__ < '1.0.1':
  99. import warnings
  100. warnings.warn("All features of ctypes interface may not work "
  101. "with ctypes < 1.0.1", stacklevel=2)
  102. # Convert path-like objects into strings
  103. libname = os.fsdecode(libname)
  104. loader_path = os.fsdecode(loader_path)
  105. ext = os.path.splitext(libname)[1]
  106. if not ext:
  107. # Try to load library with platform-specific name, otherwise
  108. # default to libname.[so|pyd]. Sometimes, these files are built
  109. # erroneously on non-linux platforms.
  110. from numpy.distutils.misc_util import get_shared_lib_extension
  111. so_ext = get_shared_lib_extension()
  112. libname_ext = [libname + so_ext]
  113. # mac, windows and linux >= py3.2 shared library and loadable
  114. # module have different extensions so try both
  115. so_ext2 = get_shared_lib_extension(is_python_ext=True)
  116. if not so_ext2 == so_ext:
  117. libname_ext.insert(0, libname + so_ext2)
  118. else:
  119. libname_ext = [libname]
  120. loader_path = os.path.abspath(loader_path)
  121. if not os.path.isdir(loader_path):
  122. libdir = os.path.dirname(loader_path)
  123. else:
  124. libdir = loader_path
  125. for ln in libname_ext:
  126. libpath = os.path.join(libdir, ln)
  127. if os.path.exists(libpath):
  128. try:
  129. return ctypes.cdll[libpath]
  130. except OSError:
  131. ## defective lib file
  132. raise
  133. ## if no successful return in the libname_ext loop:
  134. raise OSError("no file with expected extension")
  135. def _num_fromflags(flaglist):
  136. num = 0
  137. for val in flaglist:
  138. num += _flagdict[val]
  139. return num
  140. _flagnames = ['C_CONTIGUOUS', 'F_CONTIGUOUS', 'ALIGNED', 'WRITEABLE',
  141. 'OWNDATA', 'WRITEBACKIFCOPY']
  142. def _flags_fromnum(num):
  143. res = []
  144. for key in _flagnames:
  145. value = _flagdict[key]
  146. if (num & value):
  147. res.append(key)
  148. return res
  149. class _ndptr(_ndptr_base):
  150. @classmethod
  151. def from_param(cls, obj):
  152. if not isinstance(obj, ndarray):
  153. raise TypeError("argument must be an ndarray")
  154. if cls._dtype_ is not None \
  155. and obj.dtype != cls._dtype_:
  156. raise TypeError("array must have data type %s" % cls._dtype_)
  157. if cls._ndim_ is not None \
  158. and obj.ndim != cls._ndim_:
  159. raise TypeError("array must have %d dimension(s)" % cls._ndim_)
  160. if cls._shape_ is not None \
  161. and obj.shape != cls._shape_:
  162. raise TypeError("array must have shape %s" % str(cls._shape_))
  163. if cls._flags_ is not None \
  164. and ((obj.flags.num & cls._flags_) != cls._flags_):
  165. raise TypeError("array must have flags %s" %
  166. _flags_fromnum(cls._flags_))
  167. return obj.ctypes
  168. class _concrete_ndptr(_ndptr):
  169. """
  170. Like _ndptr, but with `_shape_` and `_dtype_` specified.
  171. Notably, this means the pointer has enough information to reconstruct
  172. the array, which is not generally true.
  173. """
  174. def _check_retval_(self):
  175. """
  176. This method is called when this class is used as the .restype
  177. attribute for a shared-library function, to automatically wrap the
  178. pointer into an array.
  179. """
  180. return self.contents
  181. @property
  182. def contents(self):
  183. """
  184. Get an ndarray viewing the data pointed to by this pointer.
  185. This mirrors the `contents` attribute of a normal ctypes pointer
  186. """
  187. full_dtype = _dtype((self._dtype_, self._shape_))
  188. full_ctype = ctypes.c_char * full_dtype.itemsize
  189. buffer = ctypes.cast(self, ctypes.POINTER(full_ctype)).contents
  190. return frombuffer(buffer, dtype=full_dtype).squeeze(axis=0)
  191. # Factory for an array-checking class with from_param defined for
  192. # use with ctypes argtypes mechanism
  193. _pointer_type_cache = {}
  194. def ndpointer(dtype=None, ndim=None, shape=None, flags=None):
  195. """
  196. Array-checking restype/argtypes.
  197. An ndpointer instance is used to describe an ndarray in restypes
  198. and argtypes specifications. This approach is more flexible than
  199. using, for example, ``POINTER(c_double)``, since several restrictions
  200. can be specified, which are verified upon calling the ctypes function.
  201. These include data type, number of dimensions, shape and flags. If a
  202. given array does not satisfy the specified restrictions,
  203. a ``TypeError`` is raised.
  204. Parameters
  205. ----------
  206. dtype : data-type, optional
  207. Array data-type.
  208. ndim : int, optional
  209. Number of array dimensions.
  210. shape : tuple of ints, optional
  211. Array shape.
  212. flags : str or tuple of str
  213. Array flags; may be one or more of:
  214. - C_CONTIGUOUS / C / CONTIGUOUS
  215. - F_CONTIGUOUS / F / FORTRAN
  216. - OWNDATA / O
  217. - WRITEABLE / W
  218. - ALIGNED / A
  219. - WRITEBACKIFCOPY / X
  220. Returns
  221. -------
  222. klass : ndpointer type object
  223. A type object, which is an ``_ndtpr`` instance containing
  224. dtype, ndim, shape and flags information.
  225. Raises
  226. ------
  227. TypeError
  228. If a given array does not satisfy the specified restrictions.
  229. Examples
  230. --------
  231. >>> clib.somefunc.argtypes = [np.ctypeslib.ndpointer(dtype=np.float64,
  232. ... ndim=1,
  233. ... flags='C_CONTIGUOUS')]
  234. ... #doctest: +SKIP
  235. >>> clib.somefunc(np.array([1, 2, 3], dtype=np.float64))
  236. ... #doctest: +SKIP
  237. """
  238. # normalize dtype to an Optional[dtype]
  239. if dtype is not None:
  240. dtype = _dtype(dtype)
  241. # normalize flags to an Optional[int]
  242. num = None
  243. if flags is not None:
  244. if isinstance(flags, str):
  245. flags = flags.split(',')
  246. elif isinstance(flags, (int, integer)):
  247. num = flags
  248. flags = _flags_fromnum(num)
  249. elif isinstance(flags, flagsobj):
  250. num = flags.num
  251. flags = _flags_fromnum(num)
  252. if num is None:
  253. try:
  254. flags = [x.strip().upper() for x in flags]
  255. except Exception as e:
  256. raise TypeError("invalid flags specification") from e
  257. num = _num_fromflags(flags)
  258. # normalize shape to an Optional[tuple]
  259. if shape is not None:
  260. try:
  261. shape = tuple(shape)
  262. except TypeError:
  263. # single integer -> 1-tuple
  264. shape = (shape,)
  265. cache_key = (dtype, ndim, shape, num)
  266. try:
  267. return _pointer_type_cache[cache_key]
  268. except KeyError:
  269. pass
  270. # produce a name for the new type
  271. if dtype is None:
  272. name = 'any'
  273. elif dtype.names is not None:
  274. name = str(id(dtype))
  275. else:
  276. name = dtype.str
  277. if ndim is not None:
  278. name += "_%dd" % ndim
  279. if shape is not None:
  280. name += "_"+"x".join(str(x) for x in shape)
  281. if flags is not None:
  282. name += "_"+"_".join(flags)
  283. if dtype is not None and shape is not None:
  284. base = _concrete_ndptr
  285. else:
  286. base = _ndptr
  287. klass = type("ndpointer_%s"%name, (base,),
  288. {"_dtype_": dtype,
  289. "_shape_" : shape,
  290. "_ndim_" : ndim,
  291. "_flags_" : num})
  292. _pointer_type_cache[cache_key] = klass
  293. return klass
  294. if ctypes is not None:
  295. def _ctype_ndarray(element_type, shape):
  296. """ Create an ndarray of the given element type and shape """
  297. for dim in shape[::-1]:
  298. element_type = dim * element_type
  299. # prevent the type name include np.ctypeslib
  300. element_type.__module__ = None
  301. return element_type
  302. def _get_scalar_type_map():
  303. """
  304. Return a dictionary mapping native endian scalar dtype to ctypes types
  305. """
  306. ct = ctypes
  307. simple_types = [
  308. ct.c_byte, ct.c_short, ct.c_int, ct.c_long, ct.c_longlong,
  309. ct.c_ubyte, ct.c_ushort, ct.c_uint, ct.c_ulong, ct.c_ulonglong,
  310. ct.c_float, ct.c_double,
  311. ct.c_bool,
  312. ]
  313. return {_dtype(ctype): ctype for ctype in simple_types}
  314. _scalar_type_map = _get_scalar_type_map()
  315. def _ctype_from_dtype_scalar(dtype):
  316. # swapping twice ensure that `=` is promoted to <, >, or |
  317. dtype_with_endian = dtype.newbyteorder('S').newbyteorder('S')
  318. dtype_native = dtype.newbyteorder('=')
  319. try:
  320. ctype = _scalar_type_map[dtype_native]
  321. except KeyError as e:
  322. raise NotImplementedError(
  323. "Converting {!r} to a ctypes type".format(dtype)
  324. ) from None
  325. if dtype_with_endian.byteorder == '>':
  326. ctype = ctype.__ctype_be__
  327. elif dtype_with_endian.byteorder == '<':
  328. ctype = ctype.__ctype_le__
  329. return ctype
  330. def _ctype_from_dtype_subarray(dtype):
  331. element_dtype, shape = dtype.subdtype
  332. ctype = _ctype_from_dtype(element_dtype)
  333. return _ctype_ndarray(ctype, shape)
  334. def _ctype_from_dtype_structured(dtype):
  335. # extract offsets of each field
  336. field_data = []
  337. for name in dtype.names:
  338. field_dtype, offset = dtype.fields[name][:2]
  339. field_data.append((offset, name, _ctype_from_dtype(field_dtype)))
  340. # ctypes doesn't care about field order
  341. field_data = sorted(field_data, key=lambda f: f[0])
  342. if len(field_data) > 1 and all(offset == 0 for offset, name, ctype in field_data):
  343. # union, if multiple fields all at address 0
  344. size = 0
  345. _fields_ = []
  346. for offset, name, ctype in field_data:
  347. _fields_.append((name, ctype))
  348. size = max(size, ctypes.sizeof(ctype))
  349. # pad to the right size
  350. if dtype.itemsize != size:
  351. _fields_.append(('', ctypes.c_char * dtype.itemsize))
  352. # we inserted manual padding, so always `_pack_`
  353. return type('union', (ctypes.Union,), dict(
  354. _fields_=_fields_,
  355. _pack_=1,
  356. __module__=None,
  357. ))
  358. else:
  359. last_offset = 0
  360. _fields_ = []
  361. for offset, name, ctype in field_data:
  362. padding = offset - last_offset
  363. if padding < 0:
  364. raise NotImplementedError("Overlapping fields")
  365. if padding > 0:
  366. _fields_.append(('', ctypes.c_char * padding))
  367. _fields_.append((name, ctype))
  368. last_offset = offset + ctypes.sizeof(ctype)
  369. padding = dtype.itemsize - last_offset
  370. if padding > 0:
  371. _fields_.append(('', ctypes.c_char * padding))
  372. # we inserted manual padding, so always `_pack_`
  373. return type('struct', (ctypes.Structure,), dict(
  374. _fields_=_fields_,
  375. _pack_=1,
  376. __module__=None,
  377. ))
  378. def _ctype_from_dtype(dtype):
  379. if dtype.fields is not None:
  380. return _ctype_from_dtype_structured(dtype)
  381. elif dtype.subdtype is not None:
  382. return _ctype_from_dtype_subarray(dtype)
  383. else:
  384. return _ctype_from_dtype_scalar(dtype)
  385. def as_ctypes_type(dtype):
  386. r"""
  387. Convert a dtype into a ctypes type.
  388. Parameters
  389. ----------
  390. dtype : dtype
  391. The dtype to convert
  392. Returns
  393. -------
  394. ctype
  395. A ctype scalar, union, array, or struct
  396. Raises
  397. ------
  398. NotImplementedError
  399. If the conversion is not possible
  400. Notes
  401. -----
  402. This function does not losslessly round-trip in either direction.
  403. ``np.dtype(as_ctypes_type(dt))`` will:
  404. - insert padding fields
  405. - reorder fields to be sorted by offset
  406. - discard field titles
  407. ``as_ctypes_type(np.dtype(ctype))`` will:
  408. - discard the class names of `ctypes.Structure`\ s and
  409. `ctypes.Union`\ s
  410. - convert single-element `ctypes.Union`\ s into single-element
  411. `ctypes.Structure`\ s
  412. - insert padding fields
  413. """
  414. return _ctype_from_dtype(_dtype(dtype))
  415. def as_array(obj, shape=None):
  416. """
  417. Create a numpy array from a ctypes array or POINTER.
  418. The numpy array shares the memory with the ctypes object.
  419. The shape parameter must be given if converting from a ctypes POINTER.
  420. The shape parameter is ignored if converting from a ctypes array
  421. """
  422. if isinstance(obj, ctypes._Pointer):
  423. # convert pointers to an array of the desired shape
  424. if shape is None:
  425. raise TypeError(
  426. 'as_array() requires a shape argument when called on a '
  427. 'pointer')
  428. p_arr_type = ctypes.POINTER(_ctype_ndarray(obj._type_, shape))
  429. obj = ctypes.cast(obj, p_arr_type).contents
  430. return asarray(obj)
  431. def as_ctypes(obj):
  432. """Create and return a ctypes object from a numpy array. Actually
  433. anything that exposes the __array_interface__ is accepted."""
  434. ai = obj.__array_interface__
  435. if ai["strides"]:
  436. raise TypeError("strided arrays not supported")
  437. if ai["version"] != 3:
  438. raise TypeError("only __array_interface__ version 3 supported")
  439. addr, readonly = ai["data"]
  440. if readonly:
  441. raise TypeError("readonly arrays unsupported")
  442. # can't use `_dtype((ai["typestr"], ai["shape"]))` here, as it overflows
  443. # dtype.itemsize (gh-14214)
  444. ctype_scalar = as_ctypes_type(ai["typestr"])
  445. result_type = _ctype_ndarray(ctype_scalar, ai["shape"])
  446. result = result_type.from_address(addr)
  447. result.__keep = obj
  448. return result