Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pyroaring/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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:
...
Expand Down Expand Up @@ -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:
...
Expand Down
42 changes: 41 additions & 1 deletion pyroaring/abstract_bitmap.pxi
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -174,6 +174,26 @@ cdef class AbstractBitMap:
(<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 <uintptr_t>self._c_bitmap

@property
def copy_on_write(self):
"""
Expand Down Expand Up @@ -880,6 +900,26 @@ cdef class AbstractBitMap64:
(<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 <uintptr_t>self._c_bitmap

@property
def copy_on_write(self):
"""
Expand Down
28 changes: 28 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
Loading