From e30b8e1d65bd22b071f2e78de9b4e247f4966f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Ban=C3=A1k?= <58399088+Filip62@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:33:21 +0200 Subject: [PATCH] Introduce a property to get the pointer to the underlying bitmap This is useful for FFI. --- pyroaring/__init__.pyi | 8 +++++++ pyroaring/abstract_bitmap.pxi | 42 ++++++++++++++++++++++++++++++++++- test.py | 28 +++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/pyroaring/__init__.pyi b/pyroaring/__init__.pyi index 176d478..3c5ca34 100644 --- a/pyroaring/__init__.pyi +++ b/pyroaring/__init__.pyi @@ -32,6 +32,10 @@ class AbstractBitMap: def __init__(self, values: Iterable[int] | None = None, copy_on_write: bool = False, optimize: bool = True) -> None: ... + @property + def raw_pointer(self) -> int: + ... + @property def copy_on_write(self) -> bool: ... @@ -256,6 +260,10 @@ class AbstractBitMap64: def __init__(self, values: Iterable[int] | None = None, copy_on_write: bool = False, optimize: bool = True) -> None: ... + @property + def raw_pointer(self) -> int: + ... + @property def copy_on_write(self) -> bool: ... diff --git a/pyroaring/abstract_bitmap.pxi b/pyroaring/abstract_bitmap.pxi index f7079c0..a07cd38 100644 --- a/pyroaring/abstract_bitmap.pxi +++ b/pyroaring/abstract_bitmap.pxi @@ -1,5 +1,5 @@ cimport croaring -from libc.stdint cimport uint32_t, uint64_t, int64_t +from libc.stdint cimport uint32_t, uint64_t, uintptr_t, int64_t from libcpp cimport bool from libcpp.vector cimport vector from libc.stdlib cimport free, malloc @@ -174,6 +174,26 @@ cdef class AbstractBitMap: (bm)._c_bitmap = ptr return bm + @property + def raw_pointer(self): + """ + The address of the underlying `roaring_bitmap_t`, as a Python int. + + Intended for FFI interop, e.g. passing the bitmap to another + native extension via CFFI or ctypes without a copy. + + Caveats (the caller is responsible for all of these): + + - The pointer is owned by this Python object. It is only valid + while the object is alive; the caller must not pass it to + `roaring_bitmap_free`. + + - Mutating the bitmap through this pointer on a `FrozenBitMap` + is undefined behaviour: it silently invalidates the cached + hash and breaks set/dict semantics. + """ + return self._c_bitmap + @property def copy_on_write(self): """ @@ -880,6 +900,26 @@ cdef class AbstractBitMap64: (bm)._c_bitmap = ptr return bm + @property + def raw_pointer(self): + """ + The address of the underlying `roaring_bitmap_t`, as a Python int. + + Intended for FFI interop, e.g. passing the bitmap to another + native extension via CFFI or ctypes without a copy. + + Caveats (the caller is responsible for all of these): + + - The pointer is owned by this Python object. It is only valid + while the object is alive; the caller must not pass it to + `roaring_bitmap_free`. + + - Mutating the bitmap through this pointer on a `FrozenBitMap` + is undefined behaviour: it silently invalidates the cached + hash and breaks set/dict semantics. + """ + return self._c_bitmap + @property def copy_on_write(self): """ diff --git a/test.py b/test.py index be51da7..6a3f7c7 100755 --- a/test.py +++ b/test.py @@ -13,6 +13,7 @@ import unittest import functools import base64 +import ctypes from typing import TYPE_CHECKING from collections.abc import Set, Callable, Iterable, Iterator @@ -1857,5 +1858,32 @@ def test_version(self) -> None: self.assert_regex(r'v\d+\.\d+\.\d+', pyroaring.__croaring_version__) +class TestFFI: + def test_ffi_get_ptr(self) -> None: + bm1 = BitMap() + bm2 = BitMap() + + assert bm1.raw_pointer == bm1.raw_pointer + assert bm2.raw_pointer == bm2.raw_pointer + assert bm1.raw_pointer != bm2.raw_pointer + + @pytest.mark.skipif(sys.platform == "win32", reason="Symbols are not exported on Windows") + def test_ffi_cardinality(self) -> None: + bm = BitMap() + lib = ctypes.cdll.LoadLibrary(pyroaring.__file__) + + raw_ptr = bm.raw_pointer + ffi_ptr = ctypes.c_void_p(raw_ptr) + + if is_32_bits: + ffi_cardinality_fn = lib.roaring_bitmap_get_cardinality + else: + ffi_cardinality_fn = lib.roaring64_bitmap_get_cardinality + + assert ffi_cardinality_fn(ffi_ptr) == 0 + bm.add(42) + assert ffi_cardinality_fn(ffi_ptr) == 1 + + if __name__ == "__main__": unittest.main()