Skip to content

Commit e30b8e1

Browse files
Filip62Filip Banák
authored andcommitted
Introduce a property to get the pointer to the underlying bitmap
This is useful for FFI.
1 parent 6a6f5dd commit e30b8e1

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
"""
@@ -880,6 +900,26 @@ cdef class AbstractBitMap64:
880900
(<AbstractBitMap64>bm)._c_bitmap = ptr
881901
return bm
882902

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

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

@@ -1857,5 +1858,32 @@ def test_version(self) -> None:
18571858
self.assert_regex(r'v\d+\.\d+\.\d+', pyroaring.__croaring_version__)
18581859

18591860

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

0 commit comments

Comments
 (0)