ctypeslib.py 17 KB

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