Reference

NULL

Unique object representing a NULL address.

May be used with object pointers or passed to bindings.

Source code in src/pointers/util.py
36
37
38
39
40
class NULL:
    """Unique object representing a NULL address.

    May be used with object pointers or passed to bindings.
    """

handle(func)

Handle segment violation errors when called.

Source code in src/pointers/util.py
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
def handle(func: Callable[P, T]) -> Callable[P, T]:
    """Handle segment violation errors when called."""

    @wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
        try:
            faulthandler.disable()
            call = _handle(func, args, kwargs)

            with suppress(UnsupportedOperation):
                faulthandler.enable()

            return call
        except (RuntimeError, OSError) as e:
            msg = str(e)

            if not any(
                {
                    msg.startswith("segment violation"),
                    msg.startswith("exception: access violation"),
                }
            ):
                raise

            with suppress(UnsupportedOperation):
                faulthandler.enable()

            raise SegmentViolation(msg) from None

    return wrapper

raw_type(ct)

Set a raw ctypes type for a struct.

Source code in src/pointers/util.py
50
51
52
def raw_type(ct: Type["ctypes._CData"]) -> Any:
    """Set a raw ctypes type for a struct."""
    return RawType(ct)

stop_handler()

Shutoff the SIGSEGV handler.

Source code in src/pointers/util.py
55
56
57
def stop_handler() -> None:
    """Shutoff the SIGSEGV handler."""
    os.environ["POINTERSPY_ALLOW_SEGV"] = "1"

struct_cast(ptr)

Cast a Struct or StructPointer to a Python object.

Source code in src/pointers/util.py
91
92
93
94
@handle
def struct_cast(ptr: Union["Struct", "StructPointer"]) -> Any:
    """Cast a `Struct` or `StructPointer` to a Python object."""
    return ctypes.cast(ptr.get_existing_address(), ctypes.py_object).value

BaseAllocatedPointer

Bases: BasePointer[T], Sized, ABC

Source code in src/pointers/base_pointers.py
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
class BaseAllocatedPointer(BasePointer[T], Sized, ABC):
    @property
    @abstractmethod
    def address(self) -> Optional[int]:
        ...

    @address.setter
    def address(self, value: int) -> None:
        ...

    @property
    @abstractmethod
    def freed(self) -> bool:
        ...

    @freed.setter
    def freed(self, value: bool) -> None:
        self._freed = value

    @property
    def assigned(self) -> bool:
        """Whether the allocated memory has been assigned a value."""
        return self._assigned

    @assigned.setter
    def assigned(self, value: bool) -> None:
        self._assigned = value

    @handle
    def move(
        self,
        data: Union[BasePointer[T], T],
        unsafe: bool = False,
    ) -> None:
        add_ref(data)
        self.ensure_valid()
        from .object_pointer import to_ptr

        data_ptr = data if isinstance(data, BasePointer) else to_ptr(data)

        if (sys.version_info.minor >= 11) and (gc.is_tracked(~data_ptr)):
            remove_ref(data)
            raise RuntimeError("allocation on tracked types is not supported on 3.11+")


        ptr, byte_stream = self._make_stream_and_ptr(
            sys.getsizeof(~data_ptr),
            data_ptr.ensure(),
        )

        move_to_mem(ptr, byte_stream, unsafe=unsafe)
        self.assigned = True
        remove_ref(data)

    @handle
    def dereference(self) -> T:
        if self.freed:
            raise FreedMemoryError(
                "cannot dereference memory that has been freed",
            )

        if not self.assigned:
            raise DereferenceError(
                "cannot dereference allocated memory that has no value",
            )

        return deref(self.ensure())

    @abstractmethod
    def __add__(self, amount: int) -> "BaseAllocatedPointer[Any]":
        ...

    @abstractmethod
    def __sub__(self, amount: int) -> "BaseAllocatedPointer[Any]":
        ...

    def _cleanup(self) -> None:
        pass

    def _make_stream_and_ptr(
        self,
        size: int,
        address: int,
    ) -> Tuple["ctypes._PointerLike", bytes]:
        if self.freed:
            raise FreedMemoryError("memory has been freed")

        bytes_a = (ctypes.c_ubyte * size).from_address(address)  # fmt: off
        return self.make_ct_pointer(), bytes(bytes_a)

    @abstractmethod
    def free(self) -> None:
        """Free the memory."""
        ...

    def ensure_valid(self) -> None:
        """Ensure the memory has not been freed."""
        if self.freed:
            raise FreedMemoryError(
                f"{self} has been freed",
            )

    @property
    def size(self) -> int:
        return self._size

    @size.setter
    def size(self, value: int) -> None:
        self._size = value

assigned: bool property writable

Whether the allocated memory has been assigned a value.

ensure_valid()

Ensure the memory has not been freed.

Source code in src/pointers/base_pointers.py
448
449
450
451
452
453
def ensure_valid(self) -> None:
    """Ensure the memory has not been freed."""
    if self.freed:
        raise FreedMemoryError(
            f"{self} has been freed",
        )

free() abstractmethod

Free the memory.

Source code in src/pointers/base_pointers.py
443
444
445
446
@abstractmethod
def free(self) -> None:
    """Free the memory."""
    ...

BaseCPointer

Bases: Movable[T, 'BaseCPointer[T]'], IterDereferencable[T], Sized, ABC

Source code in src/pointers/base_pointers.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
class BaseCPointer(
    Movable[T, "BaseCPointer[T]"],
    IterDereferencable[T],
    Sized,
    ABC,
):
    def __init__(self, address: int, size: int):
        self._address = address
        self._size = size
        weakref.finalize(self, self._cleanup)

    @property
    def address(self) -> Optional[int]:
        return self._address

    def _make_stream_and_ptr(
        self,
        size: int,
        address: int,
    ) -> Tuple["ctypes._PointerLike", bytes]:
        bytes_a = (ctypes.c_ubyte * size).from_address(address)
        return self.make_ct_pointer(), bytes(bytes_a)

    @handle
    def move(
        self,
        data: Union["BaseCPointer[T]", T],
        *,
        unsafe: bool = False,
    ) -> None:
        """Move data to the target address."""
        if not isinstance(data, BaseCPointer):
            raise ValueError(
                f'"{type(data).__name__}" object is not a valid C pointer',
            )

        ptr, byte_stream = self._make_stream_and_ptr(
            data.size,
            data.ensure(),
        )
        move_to_mem(ptr, byte_stream, unsafe=unsafe, target="C data")

    def __ilshift__(self, data: Union["BaseCPointer[T]", T]):
        self.move(data)
        return self

    def __ixor__(self, data: Union["BaseCPointer[T]", T]):
        self.move(data, unsafe=True)
        return self

    @handle
    def make_ct_pointer(self):
        """Turn the pointer into a ctypes pointer."""
        return ctypes.cast(
            self.ensure(),
            ctypes.POINTER(ctypes.c_char * self.size),
        )

    @abstractmethod
    def _as_parameter_(self) -> "ctypes._CData":
        """Convert the data into something that ctypes understands."""
        ...

    @abstractmethod
    def _cleanup(self) -> None:
        ...

make_ct_pointer()

Turn the pointer into a ctypes pointer.

Source code in src/pointers/base_pointers.py
335
336
337
338
339
340
341
@handle
def make_ct_pointer(self):
    """Turn the pointer into a ctypes pointer."""
    return ctypes.cast(
        self.ensure(),
        ctypes.POINTER(ctypes.c_char * self.size),
    )

move(data, *, unsafe=False)

Move data to the target address.

Source code in src/pointers/base_pointers.py
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
@handle
def move(
    self,
    data: Union["BaseCPointer[T]", T],
    *,
    unsafe: bool = False,
) -> None:
    """Move data to the target address."""
    if not isinstance(data, BaseCPointer):
        raise ValueError(
            f'"{type(data).__name__}" object is not a valid C pointer',
        )

    ptr, byte_stream = self._make_stream_and_ptr(
        data.size,
        data.ensure(),
    )
    move_to_mem(ptr, byte_stream, unsafe=unsafe, target="C data")

BaseObjectPointer

Bases: IterDereferencable[T], BasePointer[T], ABC

Abstract class for a pointer to a Python object.

Source code in src/pointers/base_pointers.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
class BaseObjectPointer(
    IterDereferencable[T],
    BasePointer[T],
    ABC,
):
    """Abstract class for a pointer to a Python object."""

    def __init__(
        self,
        address: Optional[int],
        increment_ref: bool = False,
    ) -> None:
        """
        Args:
            address: Address of the underlying value.
            increment_ref: Should the reference count on the target object get incremented.
        """  # noqa
        self._address: Optional[int] = address

        if increment_ref and address:
            add_ref(~self)

        self._origin_size = sys.getsizeof(~self if address else None)
        weakref.finalize(self, self._cleanup)

    @handle
    def set_attr(self, key: str, value: Any) -> None:
        """Force setting an attribute on the object the pointer is looking at."""  # noqa
        v: Any = ~self  # mypy gets angry if this isnt any
        if not isinstance(~self, type):
            v = type(v)

        force_set_attr(v, key, value)

    @handle
    def assign(
        self,
        target: Nullable[Union["BaseObjectPointer[T]", T]],
    ) -> None:
        """Point to a new address.

        Args:
            target: New pointer or value to look at.
        """
        if target is NULL:
            self._address = None
            return

        new: BasePointer[T] = self._get_ptr(target)  # type: ignore

        if not isinstance(new, BaseObjectPointer):
            raise ValueError(
                "can only point to object pointer",
            )

        with suppress(NullPointerError):
            remove_ref(~self)

        self._address = new.address
        add_ref(~self)

    @property
    def address(self) -> Optional[int]:
        return self._address

    @handle
    def dereference(self) -> T:
        return deref(self.ensure())

    def __irshift__(
        self,
        value: Nullable[Union["BaseObjectPointer[T]", T]],
    ):
        self.assign(value)
        return self

    @classmethod
    @abstractmethod
    def make_from(cls, obj: T) -> "BaseObjectPointer[T]":
        """Create a new instance of the pointer.

        Args:
            obj: Object to create pointer to.

        Returns:
            Created pointer.

        Example:
            ```py
            ptr = Pointer.make_from(1)
            ```"""
        ...

    @classmethod
    def _get_ptr(cls, obj: Union[T, "BasePointer[T]"]) -> "BasePointer[T]":
        return (
            obj
            if isinstance(
                obj,
                BasePointer,
            )
            else cls.make_from(obj)
        )

    def _cleanup(self) -> None:
        if self.address:
            remove_ref(~self)

__init__(address, increment_ref=False)

Parameters:
  • address (Optional[int]) –

    Address of the underlying value.

  • increment_ref (bool, default: False ) –

    Should the reference count on the target object get incremented.

Source code in src/pointers/base_pointers.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
def __init__(
    self,
    address: Optional[int],
    increment_ref: bool = False,
) -> None:
    """
    Args:
        address: Address of the underlying value.
        increment_ref: Should the reference count on the target object get incremented.
    """  # noqa
    self._address: Optional[int] = address

    if increment_ref and address:
        add_ref(~self)

    self._origin_size = sys.getsizeof(~self if address else None)
    weakref.finalize(self, self._cleanup)

assign(target)

Point to a new address.

Parameters:
  • target (Nullable[Union[BaseObjectPointer[T], T]]) –

    New pointer or value to look at.

Source code in src/pointers/base_pointers.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
@handle
def assign(
    self,
    target: Nullable[Union["BaseObjectPointer[T]", T]],
) -> None:
    """Point to a new address.

    Args:
        target: New pointer or value to look at.
    """
    if target is NULL:
        self._address = None
        return

    new: BasePointer[T] = self._get_ptr(target)  # type: ignore

    if not isinstance(new, BaseObjectPointer):
        raise ValueError(
            "can only point to object pointer",
        )

    with suppress(NullPointerError):
        remove_ref(~self)

    self._address = new.address
    add_ref(~self)

make_from(obj) abstractmethod classmethod

Create a new instance of the pointer.

Parameters:
  • obj (T) –

    Object to create pointer to.

Returns:
Example
ptr = Pointer.make_from(1)
Source code in src/pointers/base_pointers.py
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
@classmethod
@abstractmethod
def make_from(cls, obj: T) -> "BaseObjectPointer[T]":
    """Create a new instance of the pointer.

    Args:
        obj: Object to create pointer to.

    Returns:
        Created pointer.

    Example:
        ```py
        ptr = Pointer.make_from(1)
        ```"""
    ...

set_attr(key, value)

Force setting an attribute on the object the pointer is looking at.

Source code in src/pointers/base_pointers.py
201
202
203
204
205
206
207
208
@handle
def set_attr(self, key: str, value: Any) -> None:
    """Force setting an attribute on the object the pointer is looking at."""  # noqa
    v: Any = ~self  # mypy gets angry if this isnt any
    if not isinstance(~self, type):
        v = type(v)

    force_set_attr(v, key, value)

BasePointer

Bases: Dereferencable[T], Movable[T, 'BasePointer[T]'], BasicPointer, ABC, Generic[T]

Base class representing a pointer.

Source code in src/pointers/base_pointers.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
class BasePointer(
    Dereferencable[T],
    Movable[T, "BasePointer[T]"],
    BasicPointer,
    ABC,
    Generic[T],
):
    """Base class representing a pointer."""

    @abstractmethod
    def __repr__(self) -> str:
        ...

    @abstractmethod
    def _cleanup(self) -> None:
        ...

BasicPointer

Bases: ABC

Base class representing a pointer with no operations.

Source code in src/pointers/base_pointers.py
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
class BasicPointer(ABC):
    """Base class representing a pointer with no operations."""

    @property
    @abstractmethod
    def address(self) -> Optional[int]:
        """Address that the pointer is looking at."""
        ...

    @abstractmethod
    def __repr__(self) -> str:
        ...

    @final
    def __str__(self) -> str:
        return f"{type(self).__name__}({hex(self.address or 0)})"

    @abstractmethod
    def _cleanup(self) -> None:
        ...

    @final
    def __eq__(self, data: object) -> bool:
        if not isinstance(data, BasePointer):
            return False

        return data.address == self.address

    @final
    def ensure(self) -> int:
        """Ensure that the pointer is not null.

        Raises:
            NullPointerError: Address of pointer is `None`

        Returns:
            Address of the pointer.

        Example:
            ```py
            ptr = to_ptr(NULL)
            address = ptr.ensure()  # NullPointerError
            ptr >>= 1
            address = ptr.ensure()  # works just fine
            ```"""

        if not self.address:
            raise NullPointerError("pointer is NULL")
        return self.address

address: Optional[int] abstractmethod property

Address that the pointer is looking at.

ensure()

Ensure that the pointer is not null.

Raises:
Returns:
  • int –

    Address of the pointer.

Example
ptr = to_ptr(NULL)
address = ptr.ensure()  # NullPointerError
ptr >>= 1
address = ptr.ensure()  # works just fine
Source code in src/pointers/base_pointers.py
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@final
def ensure(self) -> int:
    """Ensure that the pointer is not null.

    Raises:
        NullPointerError: Address of pointer is `None`

    Returns:
        Address of the pointer.

    Example:
        ```py
        ptr = to_ptr(NULL)
        address = ptr.ensure()  # NullPointerError
        ptr >>= 1
        address = ptr.ensure()  # works just fine
        ```"""

    if not self.address:
        raise NullPointerError("pointer is NULL")
    return self.address

Dereferencable

Bases: ABC, Generic[T]

Abstract class for an object that may be dereferenced.

Source code in src/pointers/base_pointers.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
class Dereferencable(ABC, Generic[T]):
    """Abstract class for an object that may be dereferenced."""

    @abstractmethod
    def dereference(self) -> T:
        """Dereference the pointer.

        Returns:
            Value at the pointers address."""
        ...

    @final
    def __invert__(self) -> T:
        return self.dereference()

dereference() abstractmethod

Dereference the pointer.

Returns:
  • T –

    Value at the pointers address.

Source code in src/pointers/base_pointers.py
105
106
107
108
109
110
111
@abstractmethod
def dereference(self) -> T:
    """Dereference the pointer.

    Returns:
        Value at the pointers address."""
    ...

IterDereferencable

Bases: Dereferencable[T], Generic[T]

Abstract class for an object that may be dereferenced via * (__iter__)

Source code in src/pointers/base_pointers.py
118
119
120
121
122
123
124
class IterDereferencable(Dereferencable[T], Generic[T]):
    """
    Abstract class for an object that may be dereferenced via * (`__iter__`)
    """

    def __iter__(self) -> Iterator[T]:
        return iter({self.dereference()})

Movable

Bases: ABC, Generic[T, A]

Source code in src/pointers/base_pointers.py
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
class Movable(ABC, Generic[T, A]):
    @abstractmethod
    def move(
        self,
        data: Union[A, T],
        *,
        unsafe: bool = False,
    ) -> None:
        """Move/copy a value into the memory at the pointers address."""
        ...

    def __ilshift__(self, data: Union[A, T]):
        self.move(data)
        return self

    def __ixor__(self, data: Union[A, T]):
        self.move(data, unsafe=True)
        return self

move(data, *, unsafe=False) abstractmethod

Move/copy a value into the memory at the pointers address.

Source code in src/pointers/base_pointers.py
83
84
85
86
87
88
89
90
91
@abstractmethod
def move(
    self,
    data: Union[A, T],
    *,
    unsafe: bool = False,
) -> None:
    """Move/copy a value into the memory at the pointers address."""
    ...

Sized

Bases: BasicPointer, ABC

Base class for a pointer that has a size attribute.

Source code in src/pointers/base_pointers.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
class Sized(BasicPointer, ABC):
    """Base class for a pointer that has a size attribute."""

    @property
    @abstractmethod
    def size(self) -> int:
        """Size of the target value."""
        ...

    @handle
    @final
    def make_ct_pointer(self) -> "ctypes._PointerLike":
        """Convert the address to a ctypes pointer.

        Returns:
            The created ctypes pointer.
        """
        return ctypes.cast(
            self.ensure(),
            ctypes.POINTER(ctypes.c_char * self.size),
        )

    @abstractmethod
    def _make_stream_and_ptr(
        self,
        size: int,
        address: int,
    ) -> Tuple["ctypes._PointerLike", bytes]:
        ...

size: int abstractmethod property

Size of the target value.

make_ct_pointer()

Convert the address to a ctypes pointer.

Returns:
  • _PointerLike –

    The created ctypes pointer.

Source code in src/pointers/base_pointers.py
154
155
156
157
158
159
160
161
162
163
164
165
@handle
@final
def make_ct_pointer(self) -> "ctypes._PointerLike":
    """Convert the address to a ctypes pointer.

    Returns:
        The created ctypes pointer.
    """
    return ctypes.cast(
        self.ensure(),
        ctypes.POINTER(ctypes.c_char * self.size),
    )

CArrayPointer

Bases: _CDeref[List[T]], BaseCPointer[List[T]]

Class representing a pointer to a C array.

Source code in src/pointers/c_pointer.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
class CArrayPointer(_CDeref[List[T]], BaseCPointer[List[T]]):
    """Class representing a pointer to a C array."""

    def __init__(
        self,
        address: int,
        size: int,
        length: int,
        typ: Type[T],
    ):
        self._length = length
        self._type = typ
        self._decref = False
        super().__init__(address, size)

    @property
    def decref(self) -> bool:
        return self._decref

    @property
    @handle
    def _as_parameter_(self) -> "ctypes.Array[ctypes._CData]":
        ctype = get_mapped(self._type)

        deref = (ctype * self._length).from_address(self.ensure())
        return deref

    @handle
    def dereference(self) -> List[T]:
        """Dereference the pointer."""
        array = self._as_parameter_
        return [array[i] for i in range(self._length)]  # type: ignore

    def __repr__(self) -> str:
        return f"CArrayPointer(address={self.address}, size={self.size})"

    def __getitem__(self, index: int) -> T:
        array = ~self
        return array[index]

dereference()

Dereference the pointer.

Source code in src/pointers/c_pointer.py
227
228
229
230
231
@handle
def dereference(self) -> List[T]:
    """Dereference the pointer."""
    array = self._as_parameter_
    return [array[i] for i in range(self._length)]  # type: ignore

TypedCPointer

Bases: _CDeref[T], BaseCPointer[T]

Class representing a pointer with a known type.

Source code in src/pointers/c_pointer.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
class TypedCPointer(_CDeref[T], BaseCPointer[T]):
    """Class representing a pointer with a known type."""

    def __init__(
        self,
        address: int,
        data_type: Type[T],
        size: int,
        void_p: bool = True,
        decref: bool = True,
        alt: bool = False,
    ) -> None:
        self._void_p = void_p
        super().__init__(address, size)
        self._type = data_type
        self._decref = decref
        self.alt = alt

    @property
    def size(self) -> int:
        return self._size

    @property
    def decref(self) -> bool:
        return self._decref

    @property
    def type(self):
        return self._type

    @property
    def address(self) -> Optional[int]:
        return self._address

    @property  # type: ignore
    @handle
    def _as_parameter_(self) -> ctypes._CData:
        ctype = get_mapped(self.type)

        if (ctype is ctypes.c_char_p) and (self.alt):
            deref = ctype(self.ensure())
            return deref
        else:
            deref = ctype.from_address(self.ensure())

        value = deref.value  # type: ignore

        if isinstance(value, (TypedCPointer, VoidPointer)):
            return ctypes.pointer(value._as_parameter_)  # type: ignore

        return ctypes.pointer(deref)

    @handle
    def dereference(self) -> T:
        """Dereference the pointer."""
        ctype = get_mapped(self.type)

        if (ctype is ctypes.c_char_p) and (self.alt):
            res = ctypes.c_char_p(self.ensure()).value
            return res  # type: ignore

        ptr = (
            ctype.from_address(self.ensure())
            if not self._void_p
            else ctypes.cast(
                ctypes.c_void_p(self.address), ctypes.POINTER(ctype)
            )  # fmt: off
        )
        return ptr.value if not self._void_p else ptr.contents.value  # type: ignore # noqa

    def __iter__(self) -> Iterator[T]:
        """Dereference the pointer."""
        return iter({self.dereference()})

    def __repr__(self) -> str:
        return f"TypedCPointer(address={self.address}, size={self.size})"

__iter__()

Dereference the pointer.

Source code in src/pointers/c_pointer.py
192
193
194
def __iter__(self) -> Iterator[T]:
    """Dereference the pointer."""
    return iter({self.dereference()})

dereference()

Dereference the pointer.

Source code in src/pointers/c_pointer.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
@handle
def dereference(self) -> T:
    """Dereference the pointer."""
    ctype = get_mapped(self.type)

    if (ctype is ctypes.c_char_p) and (self.alt):
        res = ctypes.c_char_p(self.ensure()).value
        return res  # type: ignore

    ptr = (
        ctype.from_address(self.ensure())
        if not self._void_p
        else ctypes.cast(
            ctypes.c_void_p(self.address), ctypes.POINTER(ctype)
        )  # fmt: off
    )
    return ptr.value if not self._void_p else ptr.contents.value  # type: ignore # noqa

VoidPointer

Bases: BaseCPointer[Any]

Class representing a void pointer to a C object.

Source code in src/pointers/c_pointer.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class VoidPointer(BaseCPointer[Any]):
    """Class representing a void pointer to a C object."""

    @property
    def size(self) -> int:
        return self._size

    @property  # type: ignore
    @handle
    def _as_parameter_(self) -> ctypes.c_void_p:
        return ctypes.c_void_p(self.address)

    @handle
    def dereference(self) -> Optional[int]:
        deref = ctypes.c_void_p.from_address(self.ensure())
        return deref.value

    def __repr__(self) -> str:
        return f"VoidPointer(address={self.address}, size={self.size})"

    def _cleanup(self) -> None:
        pass

cast(ptr, data_type)

Cast a void pointer to a typed pointer.

Source code in src/pointers/c_pointer.py
241
242
243
244
245
246
247
248
249
250
251
252
@handle
def cast(ptr: VoidPointer, data_type: Type[T]) -> TypedCPointer[T]:
    """Cast a void pointer to a typed pointer."""

    return TypedCPointer(
        ptr.ensure(),
        data_type,
        ptr.size,
        decref=False,
        void_p=True,
        alt=True,
    )

to_c_ptr(data)

Convert a python type to a pointer to a C type.

Source code in src/pointers/c_pointer.py
261
262
263
264
265
266
267
268
269
def to_c_ptr(data: T) -> TypedCPointer[T]:
    """Convert a python type to a pointer to a C type."""
    ct = map_type(data)

    add_ref(ct)
    address = ctypes.addressof(ct)
    typ = type(data)

    return TypedCPointer(address, typ, ctypes.sizeof(ct), False)

to_struct_ptr(struct)

Convert a struct to a pointer.

Source code in src/pointers/c_pointer.py
272
273
274
275
276
def to_struct_ptr(struct: A) -> "StructPointer[A]":
    """Convert a struct to a pointer."""
    from .structure import StructPointer

    return StructPointer(id(struct))

to_voidp(ptr)

Cast a typed pointer to a void pointer.

Source code in src/pointers/c_pointer.py
255
256
257
258
def to_voidp(ptr: TypedCPointer[Any]) -> VoidPointer:
    """Cast a typed pointer to a void pointer."""

    return VoidPointer(ptr.ensure(), ptr.size)

decay(func)

Automatically convert values to pointers when called.

Example
@decay
def my_function(a: str, b: Pointer[str]):
    print(a, *c)

my_function('a', 'b')
Source code in src/pointers/decay.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def decay(func: Callable[P, T]) -> Callable[..., T]:
    """Automatically convert values to pointers when called.

    Example:
        ```py
        @decay
        def my_function(a: str, b: Pointer[str]):
            print(a, *c)

        my_function('a', 'b')
        ```
    """

    @wraps(func)
    def inner(*args: P.args, **kwargs: P.kwargs) -> T:
        actual = _decay_params(func, args, kwargs)
        return func(**actual)  # type: ignore

    return inner

decay_annotated(func)

Example
@decay_annotated
def my_function(a: str, b: Annotated[str, Pointer]):
    print(a, *c)

my_function('a', 'b')
Source code in src/pointers/decay.py
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
def decay_annotated(func: Callable[P, T]) -> Callable[P, T]:
    """
    Example:
        ```py
        @decay_annotated
        def my_function(a: str, b: Annotated[str, Pointer]):
            print(a, *c)

        my_function('a', 'b')
        ```
    """

    @wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs):
        hints, actual = _make_func_params(func, args, kwargs)

        for param, hint in hints.items():
            if get_origin(hint) is not Annotated:
                continue

            hint_arg = get_args(hint)[1]

            if (hint_arg is Pointer) or (get_origin(hint_arg) is Pointer):
                actual[param] = to_ptr(actual[param])

        return func(**actual)  # type: ignore

    return wrapped

decay_wrapped(_)

Example
def my_function_wrapper(a: str, b: str, c: str) -> None:
    ...

@decay_wrapped(my_function_wrapper)
def my_function(a: str, b: str, c: Pointer[str]):
    print(a, b, *c)
    print(a, b, ~c)

my_function('a', 'b', 'c')
Source code in src/pointers/decay.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def decay_wrapped(_: Callable[P, T]) -> Callable[..., Callable[P, T]]:
    """
    Example:
        ```py
        def my_function_wrapper(a: str, b: str, c: str) -> None:
            ...

        @decay_wrapped(my_function_wrapper)
        def my_function(a: str, b: str, c: Pointer[str]):
            print(a, b, *c)
            print(a, b, ~c)

        my_function('a', 'b', 'c')
        ```
    """

    def decorator(func: Callable[..., T]) -> Callable[P, T]:
        @wraps(func)
        def wrapped(*args: P.args, **kwargs: P.kwargs):
            actual = _decay_params(func, args, kwargs)
            return func(**actual)

        return wrapped

    return decorator  # type: ignore

Struct

Abstract class representing a struct.

Source code in src/pointers/structure.py
 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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
class Struct:
    """Abstract class representing a struct."""

    _hints: Dict[str, Any]
    _void_p: List[str]
    _internal_struct: Type[ctypes.Structure]

    @classmethod
    def _convert_tc_ptr(cls, typ: Any, name: str):
        if typ is TypedCPointer:
            raise TypeError(
                "cannot instantiate: TypedCPointer has no type argument",
            )

        if getattr(typ, "__origin__", None) is TypedCPointer:
            setattr(
                cls,
                name,
                RawType(
                    ctypes.POINTER(get_mapped(typ.__args__[0])),
                ),
            )

        return typ

    @classmethod
    def _get_type(
        cls,
        ct: Type["ctypes._CData"],
        name: str,
    ) -> Type["ctypes._CData"]:
        attr = getattr(cls, name, None)

        if isinstance(attr, RawType):
            return attr.tp

        if ct is ctypes.c_void_p:
            cls._void_p.append(name)

        return ct

    def __init__(self, *args: Any, do_sync: bool = True):
        class_typ: Type[Struct] = type(self)

        if class_typ is Struct:
            raise Exception(
                "cannot instantiate Struct directly",
            )

        self._existing_address: Optional[int] = None
        self._struct = self._internal_struct(
            *[
                i if not isinstance(i, BaseCPointer) else i._as_parameter_
                for i in args  # fmt: off
            ]
        )

        if do_sync:
            self._sync()

    def __init_subclass__(cls):
        hints = cls.__annotations__
        cls._void_p = []
        cls._hints = {
            k: cls._convert_tc_ptr(v, k)
            for k, v in hints.items()
            if k not in {"_hints", "_void_p", "_internal_struct"}
        }

        class _InternalStruct(ctypes.Structure):
            _fields_ = [
                (
                    name,
                    cls._get_type(get_mapped(typ), name),
                )
                for name, typ in cls._hints.items()  # fmt: off
            ]

        cls._internal_struct = _InternalStruct

    @property
    def _as_parameter_(self) -> ctypes.Structure:
        return self._struct

    @classmethod
    def from_existing(cls, struct: ctypes.Structure) -> "Struct":
        """Build a new struct from an existing ctypes structure.

        Args:
            struct: Existing `ctypes.Structure` object

        Returns:
            Created struct object.
        """
        instance = cls(do_sync=False)
        instance._struct = struct  # type: ignore
        # mypy is getting angry here for whatever reason
        instance._sync()
        instance._existing_address = ctypes.addressof(struct)

        return instance

    @handle
    def __getattribute__(self, name: str):
        attr = super().__getattribute__(name)

        with suppress(AttributeError):
            hints = super().__getattribute__("_hints")

            if (name in hints) and (type(attr)) is bytes:
                attr = attempt_decode(attr)

        if isinstance(attr, ctypes._Pointer):  # type: ignore
            value = attr.contents
            ct = type(value)

            if ct is ctypes.c_void_p:
                add_ref(ct)
                return VoidPointer(
                    ctypes.addressof(value),
                    ctypes.sizeof(value),
                )

            py_type = get_py(ct)
            return TypedCPointer(
                ctypes.addressof(value),
                py_type,
                ctypes.sizeof(value),
                False,
            )

        if name in super().__getattribute__("_void_p"):
            ct = ctypes.c_void_p(attr)  # type: ignore
            return VoidPointer(attr, ctypes.sizeof(ct))  # type: ignore

        return attr

    def __setattr__(self, name: str, value: Any):
        if hasattr(self, "_struct"):
            self._struct.__setattr__(name, value)
        super().__setattr__(name, value)

    def _sync(self) -> None:
        for name in self._hints:
            setattr(self, name, getattr(self._struct, name))

    def __repr__(self) -> str:
        return f"<struct {type(self).__name__} at {hex(ctypes.addressof(self._struct))}>"  # noqa

    @property
    def struct(self) -> ctypes.Structure:
        """Raw internal Structure object."""
        return self._struct

    def get_existing_address(self) -> int:
        if not self._existing_address:
            raise ValueError("instance has not been created from a C struct")
        return self._existing_address

struct: ctypes.Structure property

Raw internal Structure object.

from_existing(struct) classmethod

Build a new struct from an existing ctypes structure.

Parameters:
  • struct (Structure) –

    Existing ctypes.Structure object

Returns:
  • Struct –

    Created struct object.

Source code in src/pointers/structure.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
@classmethod
def from_existing(cls, struct: ctypes.Structure) -> "Struct":
    """Build a new struct from an existing ctypes structure.

    Args:
        struct: Existing `ctypes.Structure` object

    Returns:
        Created struct object.
    """
    instance = cls(do_sync=False)
    instance._struct = struct  # type: ignore
    # mypy is getting angry here for whatever reason
    instance._sync()
    instance._existing_address = ctypes.addressof(struct)

    return instance

StructPointer

Bases: Pointer[T]

Class representing a pointer to a struct.

Source code in src/pointers/structure.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
class StructPointer(Pointer[T]):
    """Class representing a pointer to a struct."""

    def __init__(
        self,
        address: int,
        existing: Optional["Struct"] = None,
    ):
        self._existing = existing
        super().__init__(address, True)

    @property  # type: ignore
    @handle
    def _as_parameter_(
        self,
    ) -> Union[int, "ctypes._PointerLike"]:
        existing = self._existing

        if existing:
            return ctypes.pointer(existing.struct)

        return self.ensure()

    def __repr__(self) -> str:
        return f"StructPointer(address={self.address}, existing={self._existing!r})"  # noqa

    def __rich__(self) -> str:
        return f"<[bold blue]pointer[/] to struct at {str(self)}>"

    def get_existing_address(self) -> int:
        return (~self).get_existing_address()

AllocatedPointer

Bases: IterDereferencable[T], BaseAllocatedPointer[T]

Pointer to allocated memory.

Source code in src/pointers/malloc.py
 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
class AllocatedPointer(IterDereferencable[T], BaseAllocatedPointer[T]):
    """Pointer to allocated memory."""

    def __init__(
        self,
        address: int,
        size: int,
        assigned: bool = False,
        parent: AllocatedPointer[T] | None = None,
    ) -> None:
        """
        Args:
            address: Address of the allocated memory.
            size: Size of the allocated memory.
            assigned: Whether an object is currently inside the memory.
        """
        self._address = address
        self._size = size
        self._freed = False
        self._assigned = assigned
        self._parent: AllocatedPointer[T] | None = parent

    def _indexed(self, amount: int) -> AllocatedPointer[T]:
        return AllocatedPointer(
            self.ensure() + amount,
            self.size - amount,
            self.assigned,
            parent=self._parent,
        )

    def _get_parent(self) -> AllocatedPointer[T]:
        parent = self

        while parent._parent:
            parent = parent._parent

        return parent

    @property
    def freed(self) -> bool:
        return self._get_parent()._freed

    @freed.setter
    def freed(self, value: bool) -> None:
        self._get_parent()._freed = value

    @property
    def address(self) -> Optional[int]:
        return self._address

    @address.setter
    def address(self, value: int) -> None:
        self._address = value

    def __repr__(self) -> str:
        return f"AllocatedPointer(address={self.address}, size={self.size})"

    def __add__(self, amount: int) -> AllocatedPointer[T]:
        return self._indexed(amount)

    def __sub__(self, amount: int) -> AllocatedPointer[T]:
        return self._indexed(amount)

    @handle
    def free(self) -> None:
        self.ensure_valid()
        c_free(self.make_ct_pointer())
        self.freed = True

    @handle
    def __getitem__(self, index: int) -> AllocatedPointer[T]:
        if not isinstance(index, int):
            raise ValueError(
                f"memory indices must be int, not {type(index).__name__}",
            )

        return self._indexed(index)

    @handle
    def __setitem__(self, index: int, value: T) -> None:
        if not isinstance(index, int):
            raise ValueError(
                f"memory indices must be int, not {type(index).__name__}",
            )

        ptr = self._indexed(index)
        ptr <<= value

__init__(address, size, assigned=False, parent=None)

Parameters:
  • address (int) –

    Address of the allocated memory.

  • size (int) –

    Size of the allocated memory.

  • assigned (bool, default: False ) –

    Whether an object is currently inside the memory.

Source code in src/pointers/malloc.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def __init__(
    self,
    address: int,
    size: int,
    assigned: bool = False,
    parent: AllocatedPointer[T] | None = None,
) -> None:
    """
    Args:
        address: Address of the allocated memory.
        size: Size of the allocated memory.
        assigned: Whether an object is currently inside the memory.
    """
    self._address = address
    self._size = size
    self._freed = False
    self._assigned = assigned
    self._parent: AllocatedPointer[T] | None = parent

free(target)

Equivalent to target.free()

Parameters:
Example
ptr = malloc(1)
free(ptr)  # is the same as `ptr.free()`
Source code in src/pointers/malloc.py
133
134
135
136
137
138
139
140
141
142
143
144
def free(target: BaseAllocatedPointer):
    """Equivalent to `target.free()`

    Args:
        target: Pointer to free.

    Example:
        ```py
        ptr = malloc(1)
        free(ptr)  # is the same as `ptr.free()`
        ```"""
    target.free()

malloc(size)

Allocate memory of a given size.

Parameters:
  • size (int) –

    Allocation size.

Returns:
Raises:
  • AllocationError –

    Raised when allocation fails, presumably due to no memory.

Example
ptr = malloc(1)
Source code in src/pointers/malloc.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def malloc(size: int) -> AllocatedPointer[Any]:
    """Allocate memory of a given size.

    Args:
        size: Allocation size.

    Returns:
        Pointer to allocated memory.

    Raises:
        AllocationError: Raised when allocation fails, presumably due to no memory.

    Example:
        ```py
        ptr = malloc(1)
        ```
    """  # noqa
    mem = c_malloc(size)

    if not mem:
        raise AllocationError("failed to allocate memory")

    return AllocatedPointer(mem, size)

realloc(target, size)

Resize a memory block created by malloc.

Parameters:
  • target (A) –

    Pointer to reallocate.

  • size (int) –

    New allocation size.

Returns:
  • A –

    Original object.

Raises:
  • InvalidSizeError –

    Object inside allocation is larger than attempted reallocation.

  • AllocationError –

    Raised when allocation fails, presumably due to no memory.

Example
ptr = malloc(1)
realloc(ptr, 2)
Source code in src/pointers/malloc.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
@handle
def realloc(target: A, size: int) -> A:
    """Resize a memory block created by malloc.

    Args:
        target: Pointer to reallocate.
        size: New allocation size.

    Returns:
        Original object.

    Raises:
        InvalidSizeError: Object inside allocation is larger than attempted reallocation.
        AllocationError: Raised when allocation fails, presumably due to no memory.

    Example:
        ```py
        ptr = malloc(1)
        realloc(ptr, 2)
        ```
    """  # noqa
    if type(target) is StackAllocatedPointer:
        raise TypeError("pointers to items on the stack may not be resized")

    tsize: int = sys.getsizeof(~target)

    if target.assigned and (tsize > size):
        raise InvalidSizeError(
            f"object inside memory is of size {tsize}, so memory cannot be set to size {size}",  # noqa
        )

    addr = c_realloc(target.address, size)

    if not addr:
        raise AllocationError("failed to resize memory")

    target.size = size
    target.address = addr
    return target

AllocatedArrayPointer

Bases: BaseAllocatedPointer[T]

Pointer to an allocated array.

Source code in src/pointers/calloc.py
 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
114
115
116
117
118
119
120
121
122
class AllocatedArrayPointer(BaseAllocatedPointer[T]):
    """Pointer to an allocated array."""

    def __init__(
        self,
        address: int,
        chunks: int,
        chunk_size: int,
        current_index: int,
        chunk_store: Optional[Dict[int, "AllocatedArrayPointer[T]"]] = None,
        freed: bool = False,
        origin_address: Optional[int] = None,
    ) -> None:
        self._origin_address = origin_address or address
        self._address = address
        self._size = chunk_size
        self._current_index = current_index
        self._chunks = chunks
        self._chunk_store = chunk_store or {0: self}
        self._assigned = True
        self._tracked = False
        self._freed = freed

        if chunk_store:
            self._chunk_store[self.current_index] = self

    @property  # type: ignore
    def address(self) -> Optional[int]:
        return self._address

    @property
    def current_index(self) -> int:
        """Current chunk index."""
        return self._current_index

    @property
    def chunks(self) -> int:
        """Number of allocated chunks."""
        return self._chunks

    def _get_chunk_at(self, index: int) -> "AllocatedArrayPointer[T]":
        if index > self.chunks:
            raise IndexError(
                f"index is {index}, while allocation is {self.chunks}",
            )

        if index < 0:  # for handling __sub__
            raise IndexError("index is below zero")

        if index not in self._chunk_store:
            self._chunk_store[index] = AllocatedArrayPointer(
                self._origin_address + (index * self.size),
                self.chunks,
                self.size,
                index,
                self._chunk_store,
                self._freed,
                self._origin_address,
            )

        return self._chunk_store[index]

    def __add__(self, amount: int) -> "AllocatedArrayPointer[T]":
        self.ensure_valid()
        return self._get_chunk_at(self._current_index + amount)

    def __sub__(self, amount: int) -> "AllocatedArrayPointer[T]":
        return self.__add__(-amount)

    def __repr__(self) -> str:
        return f"AllocatedArrayPointer(address={self.address}, current_index={self.current_index})"  # noqa

    def __iter__(self) -> Iterator["AllocatedArrayPointer[T]"]:
        for i in range(self.current_index, self.chunks):
            yield self + i

    def __getitem__(self, index: int) -> "AllocatedArrayPointer[T]":
        return self._get_chunk_at(index)

    def __setitem__(self, index: int, value: T) -> None:
        chunk = self._get_chunk_at(index)
        chunk <<= value

    @property
    def freed(self) -> bool:
        return self._freed

    @handle
    def free(self) -> None:
        first = self[0]
        first.ensure_valid()

        for i in range(self._chunks):  # using __iter__ breaks here
            chunk = self._get_chunk_at(i)
            chunk._freed = True

        c_free(first.make_ct_pointer())

chunks: int property

Number of allocated chunks.

current_index: int property

Current chunk index.

calloc(num, size)

Allocate a number of blocks with a given size.

Source code in src/pointers/calloc.py
125
126
127
128
129
130
131
132
def calloc(num: int, size: int) -> AllocatedArrayPointer:
    """Allocate a number of blocks with a given size."""
    address: int = c_calloc(num, size)

    if not address:
        raise AllocationError("failed to allocate memory")

    return AllocatedArrayPointer(address, num, size, 0)

AllocationError

Bases: Exception

Raised when a memory allocation fails.

Source code in src/pointers/exceptions.py
14
15
class AllocationError(Exception):
    """Raised when a memory allocation fails."""

DereferenceError

Bases: Exception

Raised when dereferencing an address fails.

Source code in src/pointers/exceptions.py
18
19
class DereferenceError(Exception):
    """Raised when dereferencing an address fails."""  # noqa

FreedMemoryError

Bases: Exception

Raised when trying to perform an operation on freed memory.

Source code in src/pointers/exceptions.py
22
23
class FreedMemoryError(Exception):
    """Raised when trying to perform an operation on freed memory."""

InvalidBindingParameter

Bases: Exception

Raised when an invalid type is passed to a binding.

Source code in src/pointers/exceptions.py
30
31
class InvalidBindingParameter(Exception):
    """Raised when an invalid type is passed to a binding."""

InvalidSizeError

Bases: Exception

Raised when trying to move an object of the wrong size to an allocation.

Source code in src/pointers/exceptions.py
26
27
class InvalidSizeError(Exception):
    """Raised when trying to move an object of the wrong size to an allocation."""  # noqa

InvalidVersionError

Bases: Exception

Python version is not high enough.

Source code in src/pointers/exceptions.py
38
39
class InvalidVersionError(Exception):
    """Python version is not high enough."""

NullPointerError

Bases: Exception

Raised when a pointer is null.

Source code in src/pointers/exceptions.py
34
35
class NullPointerError(Exception):
    """Raised when a pointer is null."""

SegmentViolation

Bases: Exception

SIGSEGV was sent to Python.

Source code in src/pointers/exceptions.py
42
43
class SegmentViolation(Exception):
    """SIGSEGV was sent to Python."""

VariableLifetimeError

Bases: Exception

Variable is no longer available.

Source code in src/pointers/exceptions.py
46
47
class VariableLifetimeError(Exception):
    """Variable is no longer available."""

attempt_decode(data)

Attempt to decode a string of bytes.

Source code in src/pointers/_utils.py
74
75
76
77
78
79
def attempt_decode(data: bytes) -> Union[str, bytes]:
    """Attempt to decode a string of bytes."""
    try:
        return data.decode()
    except UnicodeDecodeError:
        return data

deref(address)

Get the value at the target address.

Source code in src/pointers/_utils.py
169
170
171
def deref(address: int) -> Any:
    """Get the value at the target address."""
    return ctypes.cast(address, ctypes.py_object).value

force_set_attr(typ, key, value)

Force setting an attribute on the target type.

Source code in src/pointers/_utils.py
158
159
160
161
162
163
164
165
166
def force_set_attr(typ: Type[Any], key: str, value: Any) -> None:
    """Force setting an attribute on the target type."""

    if not isinstance(typ, type):
        raise ValueError(
            f"{typ} does not derive from type (did you pass an instance and not a class)?",  # noqa
        )

    _force_set_attr(typ, key, value)

get_mapped(typ)

Get the C mapped value of the given type.

Source code in src/pointers/_utils.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def get_mapped(typ: Any) -> "Type[ctypes._CData]":
    """Get the C mapped value of the given type."""
    from .c_pointer import VoidPointer

    if getattr(typ, "__origin__", None) is Callable:
        args = list(typ.__args__)
        res = args.pop(-1)
        return ctypes.CFUNCTYPE(get_mapped(res), *map(get_mapped, args))

    if type(typ) in {FunctionType, MethodType}:
        hints = typ.__annotations__.copy()
        try:
            res = hints.pop("return")
        except KeyError as e:
            raise TypeError(
                "return type annotation is required to convert to a C function"  # noqa
            ) from e

        args = hints.values()
        return ctypes.CFUNCTYPE(
            get_mapped(res) if res else None,
            *map(get_mapped, args),
        )

    # VoidPointer needs to be passed here to stop circular imports
    return {**_C_TYPES, VoidPointer: ctypes.c_void_p}.get(  # type: ignore
        typ,
    ) or ctypes.py_object

get_py(data)

Map the specified C type to a Python type.

Source code in src/pointers/_utils.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
def get_py(
    data: Type["ctypes._CData"],
) -> Type[Any]:
    """Map the specified C type to a Python type."""
    from .base_pointers import BaseCPointer

    if data.__name__.startswith("LP_"):
        return BaseCPointer

    try:
        return _PY_TYPES[data]
    except KeyError as e:
        raise ValueError(
            f"{data} is not a valid ctypes type",
        ) from e

is_mappable(typ)

Whether the specified type is mappable to C.

Source code in src/pointers/_utils.py
121
122
123
124
125
126
127
def is_mappable(typ: Any) -> bool:
    """Whether the specified type is mappable to C."""
    try:
        get_mapped(typ)
        return True
    except ValueError:
        return False

make_py(data)

Convert the target C value to a Python object.

Source code in src/pointers/_utils.py
147
148
149
150
151
152
153
154
155
def make_py(data: "ctypes._CData"):
    """Convert the target C value to a Python object."""
    typ = get_py(type(data))
    res = typ(data)

    if typ is bytes:
        res = attempt_decode(res)

    return res

map_type(data)

Map the specified data to a C type.

Source code in src/pointers/_utils.py
82
83
84
85
86
87
88
89
def map_type(data: Any) -> "ctypes._CData":
    """Map the specified data to a C type."""
    typ = get_mapped(type(data))

    try:
        return typ(data)  # type: ignore
    except TypeError:
        return ctypes.py_object(data)

move_to_mem(ptr, stream, *, unsafe=False, target='memory allocation')

Move data to a C pointer.

Source code in src/pointers/_utils.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def move_to_mem(
    ptr: "ctypes._PointerLike",
    stream: bytes,
    *,
    unsafe: bool = False,
    target: str = "memory allocation",
):
    """Move data to a C pointer."""

    slen = len(stream)
    plen = len(ptr.contents)  # type: ignore

    if (slen > plen) and (not unsafe):
        raise InvalidSizeError(
            f"object is of size {slen}, while {target} is {plen}",
        )

    ctypes.memmove(ptr, stream, slen)