1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

""" 

Conversion from ctypes to dtype. 

 

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

something like:: 

 

def dtype_from_ctypes_type(t): 

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

class DummyStruct(ctypes.Structure): 

_fields_ = [('a', t)] 

 

# empty to avoid memory allocation 

ctype_0 = (DummyStruct * 0)() 

mv = memoryview(ctype_0) 

 

# convert the struct, and slice back out the field 

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

 

Unfortunately, this fails because: 

 

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

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

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

""" 

import _ctypes 

import ctypes 

 

import numpy as np 

 

 

def _from_ctypes_array(t): 

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

 

 

def _from_ctypes_structure(t): 

for item in t._fields_: 

if len(item) > 2: 

raise TypeError( 

"ctypes bitfields have no dtype equivalent") 

 

if hasattr(t, "_pack_"): 

formats = [] 

offsets = [] 

names = [] 

current_offset = 0 

for fname, ftyp in t._fields_: 

names.append(fname) 

formats.append(dtype_from_ctypes_type(ftyp)) 

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

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

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

offsets.append(current_offset) 

current_offset += ctypes.sizeof(ftyp) 

 

return np.dtype(dict( 

formats=formats, 

offsets=offsets, 

names=names, 

itemsize=ctypes.sizeof(t))) 

else: 

fields = [] 

for fname, ftyp in t._fields_: 

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

 

# by default, ctypes structs are aligned 

return np.dtype(fields, align=True) 

 

 

def _from_ctypes_scalar(t): 

""" 

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

""" 

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

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

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

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

else: 

return np.dtype(t._type_) 

 

 

def _from_ctypes_union(t): 

formats = [] 

offsets = [] 

names = [] 

for fname, ftyp in t._fields_: 

names.append(fname) 

formats.append(dtype_from_ctypes_type(ftyp)) 

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

 

return np.dtype(dict( 

formats=formats, 

offsets=offsets, 

names=names, 

itemsize=ctypes.sizeof(t))) 

 

 

def dtype_from_ctypes_type(t): 

""" 

Construct a dtype object from a ctypes type 

""" 

if issubclass(t, _ctypes.Array): 

return _from_ctypes_array(t) 

elif issubclass(t, _ctypes._Pointer): 

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

elif issubclass(t, _ctypes.Structure): 

return _from_ctypes_structure(t) 

elif issubclass(t, _ctypes.Union): 

return _from_ctypes_union(t) 

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

return _from_ctypes_scalar(t) 

else: 

raise NotImplementedError( 

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