dtypes
provides decorators to make it easier to work with ctypes
.
pip install dtypes
This example showcases how to use type annotations to declare structs for a HalfEdge data structure. It uses type annotations and simplified forward declarations.
from dtypes.structify import structify
from dtypes.fwd import fwd, ThisPtr, Pointer
HalfEdge = fwd()
Face = fwd()
@structify
class Vertex(ctypes.structure):
pos : ctypes.c_float * 3
edge : Pointer(HalfEdge)
@structify
class Face(ctypes.structure, Face):
edge : Pointer(HalfEdge)
@structify
class HalfEdge(ctypes.structure, HalfEdge):
vertex : Pointer(Vertex)
face : Pointer(Face)
next_edge : ThisPtr
reverse : ThisPtr
Helper mixin that prints contents of fields in repr (no py members tho)
>>> from .typedefs import uint16_t
>>> @structify
... class PrintyTesty(ctypes.Structure, ReprStructureMembers):
... packed: (uint16_t, 1)
... ctor: (uint16_t, 1)
... overloaded_operators: (uint16_t, 1)
>>> print(PrintyTesty(True, False, True))
PrintyTesty{'packed': 1, 'ctor': 0, 'overloaded_operators': 1}
There is also Structy
to inherit from directly that mixes this into ctypes.structure
class Structy(ctypes.Structure, ReprStructureMembers):
Given Type
, returns a ctypes-structure containing only that type.
This allows us to access the variable in a structure, without losing
the binding/address of the variable, as it works if you work with
the plain ctype
>>> from dtypes.typedef import BasicWrapper, uint32_t
>>> from dtypes.structify import structify
>>> @structify
... class Example(ctypes.Structure):
... plain : uint32_t
... wrapped : BasicWrapper(uint32_t)
>>> example = Example()
>>> type(example.plain)
<class 'int'>
>>> type(example.wrapped)
<class 'dtypes.typedefs.uint32_tw'>
>>> ctypes.addressof(example.plain)
Traceback (most recent call last):
...
TypeError: invalid type
>>> ctypes.addressof(example.wrapped) - ctypes.addressof(example)
4
c_void_p is returned as a "int" when accessed inside structs, losing the type 😒
Thus there is dtypes.voidy.Voidy
which is an empty struct, and dtypes.voidy.VoidyPtr
which is a pointer to that empty struct. It can be used as a better way to preserve
the pointer-ness of the type, and like with BasicWrapper
it preserves the address
of members when accessed.
dtypes.offsetof
returns the number of bits of offset from the start of a structure to a member.
Comparing pointer address values is slightly tricky in ctypes.
Thus there is dtypes.cmp_ptr
to make it easier.
def cmp_ptr(ptr1, ptr2):
return ctypes.addressof(ptr1.contents) == ctypes.addressof(ptr2.contents)