ctypeslib.py 17 KB

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