1"""

2 Conversion from ctypes to dtype.

3

4 In an ideal world, we could achieve this through the PEP3118 buffer protocol,

5 something like::

6

7 def dtype_from_ctypes_type(t):

8 # needed to ensure that the shape of `t` is within memoryview.format

9 class DummyStruct(ctypes.Structure):

10 _fields_ = [('a', t)]

11

12 # empty to avoid memory allocation

13 ctype_0 = (DummyStruct * 0)()

14 mv = memoryview(ctype_0)

15

16 # convert the struct, and slice back out the field

17 return _dtype_from_pep3118(mv.format)['a']

18

19 Unfortunately, this fails because:

20

21* ctypes cannot handle length-0 arrays with PEP3118 (bpo-32782)

22* PEP3118 cannot represent unions, but both numpy and ctypes can

23* ctypes cannot handle big-endian structs with PEP3118 (bpo-32780)

24"""

25

26# We delay-import ctypes for distributions that do not include it.

27# While this module is not used unless the user passes in ctypes

28# members, it is eagerly imported from numpy/core/__init__.py.

29 import numpy as np

30

31

32 def _from_ctypes_array(t):

33 return np.dtype((dtype_from_ctypes_type(t._type_), (t._length_,)))

34

35

36 def _from_ctypes_structure(t):

37 for item in t._fields_:

38 if len(item) > 2:

39 raise TypeError(

40 "ctypes bitfields have no dtype equivalent")

41

42 if hasattr(t, "_pack_"):

43 import ctypes

44 formats = []

45 offsets = []

46 names = []

47 current_offset = 0

48 for fname, ftyp in t._fields_:

49 names.append(fname)

50 formats.append(dtype_from_ctypes_type(ftyp))

51 # Each type has a default offset, this is platform dependent for some types.

52 effective_pack = min(t._pack_, ctypes.alignment(ftyp))

53 current_offset = ((current_offset + effective_pack - 1) // effective_pack) * effective_pack

54 offsets.append(current_offset)

55 current_offset += ctypes.sizeof(ftyp)

56

57 return np.dtype(dict(

58 formats=formats,

59 offsets=offsets,

60 names=names,

61 itemsize=ctypes.sizeof(t)))

62 else:

63 fields = []

64 for fname, ftyp in t._fields_:

65 fields.append((fname, dtype_from_ctypes_type(ftyp)))

66

67 # by default, ctypes structs are aligned

68 return np.dtype(fields, align=True)

69

70

71 def _from_ctypes_scalar(t):

72 """

73 Return the dtype type with endianness included if it's the case

74 """

75 if getattr(t, '__ctype_be__', None) is t:

76 return np.dtype('>' + t._type_)

77 elif getattr(t, '__ctype_le__', None) is t:

78 return np.dtype('<' + t._type_)

79 else:

80 return np.dtype(t._type_)

81

82

83 def _from_ctypes_union(t):

84 import ctypes

85 formats = []

86 offsets = []

87 names = []

88 for fname, ftyp in t._fields_:

89 names.append(fname)

90 formats.append(dtype_from_ctypes_type(ftyp))

91 offsets.append(0) # Union fields are offset to 0

92

93 return np.dtype(dict(

94 formats=formats,

95 offsets=offsets,

96 names=names,

97 itemsize=ctypes.sizeof(t)))

98

99

100 def dtype_from_ctypes_type(t):

101 """

102 Construct a dtype object from a ctypes type

103 """

104 import _ctypes

105 if issubclass(t, _ctypes.Array):

106 return _from_ctypes_array(t)

107 elif issubclass(t, _ctypes._Pointer):

108 raise TypeError("ctypes pointers have no dtype equivalent")

109 elif issubclass(t, _ctypes.Structure):

110 return _from_ctypes_structure(t)

111 elif issubclass(t, _ctypes.Union):

112 return _from_ctypes_union(t)

113 elif isinstance(getattr(t, '_type_', None), str):

114 return _from_ctypes_scalar(t)

115 else:

116 raise NotImplementedError(

117 "Unknown ctypes type {}".format(t.__name__))