Skip to content

Commit a21aff1

Browse files
authored
Introduce a field raw_pointer to get the pointer to the underlying bitmap (#144)
This is useful for FFI.
1 parent a77da03 commit a21aff1

3 files changed

Lines changed: 77 additions & 1 deletion

File tree

pyroaring/__init__.pyi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ class AbstractBitMap:
3232
def __init__(self, values: Iterable[int] | None = None, copy_on_write: bool = False, optimize: bool = True) -> None:
3333
...
3434

35+
@property
36+
def raw_pointer(self) -> int:
37+
...
38+
3539
@property
3640
def copy_on_write(self) -> bool:
3741
...
@@ -256,6 +260,10 @@ class AbstractBitMap64:
256260
def __init__(self, values: Iterable[int] | None = None, copy_on_write: bool = False, optimize: bool = True) -> None:
257261
...
258262

263+
@property
264+
def raw_pointer(self) -> int:
265+
...
266+
259267
@property
260268
def copy_on_write(self) -> bool:
261269
...

pyroaring/abstract_bitmap.pxi

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
cimport croaring
2-
from libc.stdint cimport uint32_t, uint64_t, int64_t
2+
from libc.stdint cimport uint32_t, uint64_t, uintptr_t, int64_t
33
from libcpp cimport bool
44
from libcpp.vector cimport vector
55
from libc.stdlib cimport free, malloc
@@ -174,6 +174,26 @@ cdef class AbstractBitMap:
174174
(<AbstractBitMap>bm)._c_bitmap = ptr
175175
return bm
176176

177+
@property
178+
def raw_pointer(self):
179+
"""
180+
The address of the underlying `roaring_bitmap_t`, as a Python int.
181+
182+
Intended for FFI interop, e.g. passing the bitmap to another
183+
native extension via CFFI or ctypes without a copy.
184+
185+
Caveats (the caller is responsible for all of these):
186+
187+
- The pointer is owned by this Python object. It is only valid
188+
while the object is alive; the caller must not pass it to
189+
`roaring_bitmap_free`.
190+
191+
- Mutating the bitmap through this pointer on a `FrozenBitMap`
192+
is undefined behaviour: it silently invalidates the cached
193+
hash and breaks set/dict semantics.
194+
"""
195+
return <uintptr_t>self._c_bitmap
196+
177197
@property
178198
def copy_on_write(self):
179199
"""
@@ -884,6 +904,26 @@ cdef class AbstractBitMap64:
884904
(<AbstractBitMap64>bm)._c_bitmap = ptr
885905
return bm
886906

907+
@property
908+
def raw_pointer(self):
909+
"""
910+
The address of the underlying `roaring_bitmap_t`, as a Python int.
911+
912+
Intended for FFI interop, e.g. passing the bitmap to another
913+
native extension via CFFI or ctypes without a copy.
914+
915+
Caveats (the caller is responsible for all of these):
916+
917+
- The pointer is owned by this Python object. It is only valid
918+
while the object is alive; the caller must not pass it to
919+
`roaring_bitmap_free`.
920+
921+
- Mutating the bitmap through this pointer on a `FrozenBitMap`
922+
is undefined behaviour: it silently invalidates the cached
923+
hash and breaks set/dict semantics.
924+
"""
925+
return <uintptr_t>self._c_bitmap
926+
887927
@property
888928
def copy_on_write(self):
889929
"""

test.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import unittest
1414
import functools
1515
import base64
16+
import ctypes
1617
from typing import TYPE_CHECKING
1718
from collections.abc import Set, Callable, Iterable, Iterator
1819

@@ -1861,5 +1862,32 @@ def test_version(self) -> None:
18611862
self.assert_regex(r'v\d+\.\d+\.\d+', pyroaring.__croaring_version__)
18621863

18631864

1865+
class TestFFI:
1866+
def test_ffi_get_ptr(self) -> None:
1867+
bm1 = BitMap()
1868+
bm2 = BitMap()
1869+
1870+
assert bm1.raw_pointer == bm1.raw_pointer
1871+
assert bm2.raw_pointer == bm2.raw_pointer
1872+
assert bm1.raw_pointer != bm2.raw_pointer
1873+
1874+
@pytest.mark.skipif(sys.platform == "win32", reason="Symbols are not exported on Windows")
1875+
def test_ffi_cardinality(self) -> None:
1876+
bm = BitMap()
1877+
lib = ctypes.cdll.LoadLibrary(pyroaring.__file__)
1878+
1879+
raw_ptr = bm.raw_pointer
1880+
ffi_ptr = ctypes.c_void_p(raw_ptr)
1881+
1882+
if is_32_bits:
1883+
ffi_cardinality_fn = lib.roaring_bitmap_get_cardinality
1884+
else:
1885+
ffi_cardinality_fn = lib.roaring64_bitmap_get_cardinality
1886+
1887+
assert ffi_cardinality_fn(ffi_ptr) == 0
1888+
bm.add(42)
1889+
assert ffi_cardinality_fn(ffi_ptr) == 1
1890+
1891+
18641892
if __name__ == "__main__":
18651893
unittest.main()

0 commit comments

Comments
 (0)