Skip to content

Commit 56c0aa0

Browse files
committed
wraps via a , to facilitate code generation on top of a C API allowing to be passed in.
1 parent d224e28 commit 56c0aa0

File tree

6 files changed

+104
-24
lines changed

6 files changed

+104
-24
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
<!--next-version-placeholder-->
44

5+
## v1.2.0 (2023-01-25)
6+
7+
`wrap_as_pointer_handle` wraps `None` via a `GenericWrapper`, to facilitate code generation on top of a C API allowing `nullptr` to be passed in.
8+
59
## v1.1.1 (2022-08-19)
610

711
Minor changes that may not have been required to build the [conda package](https://github.com/conda-forge/refcount-feedstock/pull/2)

docs/tech_notes.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,55 @@
44

55
Note to self: as of Jan 2019 also using github_jm\didactique\doc\know_how.md to log the exploratory and release processes around `refcount`
66

7+
## Poetry
8+
9+
2022-12
10+
11+
Having to update dependent package versions [GitPython vulnerable to Remote Code Execution due to improper user input validation](https://github.com/csiro-hydroinformatics/pyrefcount/security/dependabot/4). It is probably not a burning issue given this should be only a dev-time dependency and certainly not runtime (otherwise poetry is crap)
12+
13+
```sh
14+
cd ~/src/pyrefcount
15+
poetry --version # in a conda env for poetry...
16+
```
17+
18+
`ModuleNotFoundError: No module named 'importlib_metadata'`. Right...
19+
20+
```sh
21+
cd ~/bin
22+
curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj micromamba
23+
micromamba shell init -s bash -p ~/micromamba
24+
```
25+
26+
```sh
27+
source ~/.bashrc
28+
alias mm=micromamba
29+
mm create -n poetry python=3.9
30+
mm activate poetry
31+
mm list
32+
```
33+
34+
`pip install poetry`
35+
36+
```
37+
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
38+
plantuml-markdown 3.6.3 requires Markdown, which is not installed.
39+
```
40+
41+
Huh??
42+
43+
`pip install Markdown` seems to fix it. Odd.
44+
45+
poetry --version returns 1.3.1 which seems to be what is available from conda-forge anyway. So:
46+
47+
```
48+
mm deactivate
49+
mm env remove -n poetry
50+
51+
mm create -n poetry python=3.9 poetry=1.3.1
52+
mm activate poetry
53+
mm list
54+
```
55+
756
## Release steps
857

958
* all UT pass

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "refcount"
3-
version = "1.1.1"
3+
version = "1.2.0"
44
description = "Python classes for reference counting"
55
authors = ["J-M <[email protected]>"]
66
license = "LICENSE.txt"

refcount/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
66
Doing it this way provides for access in setup.py and via __version__
77
"""
8-
__version__ = "1.1.1"
8+
__version__ = "1.2.0"

refcount/interop.py

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,51 @@
1919
class CffiNativeHandle(NativeHandle):
2020
"""Reference counting wrapper class for CFFI pointers
2121
22+
This class is originally inspired from a class with a similar purpose in C#. See https://github.com/rdotnet/dynamic-interop-dll
23+
24+
Say you have a C API as follows:
25+
26+
* `void* create_some_object();`
27+
* `dispose_of_some_object(void* obj);`
28+
29+
and accessing it using Python and [CFFI](https://cffi.readthedocs.io).
30+
Users would use the `calllib` function:
31+
32+
```python
33+
from cffi import FFI
34+
ffi = FFI()
35+
36+
# cdef() expects a single string declaring the C types, functions and
37+
# globals needed to use the shared object. It must be in valid C syntax.
38+
ffi.cdef('''
39+
void* create_some_object();
40+
dispose_of_some_object(void* obj);
41+
''')
42+
mydll_so = ffi.dlopen('/path/to/mydll.so')
43+
cffi_void_ptr = mydll_so.create_some_object()
44+
```
45+
46+
at some point when done you need to dispose of it to clear native memory:
47+
48+
```python
49+
mydll_so.dispose_of_some_object(cffi_void_ptr)
50+
```
51+
52+
In practice in real systems one quickly ends up with cases
53+
where it is unclear when to dispose of the object.
54+
If you call the `dispose_of_some_object` function more
55+
than once, or too soon, you quickly crash the program, or possibly worse outcomes with numeric non-sense.
56+
`CffiNativeHandle` is designed to alleviate this headache by
57+
using native reference counting of `handle` classes to reliably dispose of objects.
58+
2259
Attributes:
2360
_handle (object): The handle (e.g. cffi pointer) to the native resource.
2461
_type_id (Optional[str]): An optional identifier for the type of underlying resource. This can be used to usefully maintain type information about the pointer/handle across an otherwise opaque C API. See package documentation.
2562
_finalizing (bool): a flag telling whether this object is in its deletion phase. This has a use in some advanced cases with reverse callback, possibly not relevant in Python.
2663
"""
2764

28-
# Say you have a C API as follows:
29-
# * `void* create_some_object();`
30-
# * `dispose_of_some_object(void* obj);`
31-
# and accessing it using Python and CFFI. Users would use the `calllib` function:
32-
# aLibPointer = callib('mylib', 'create_some_object');
33-
# but at some point when done need to dispose of it:
34-
# callib('mylib', 'dispose_of_some_object', aLibPointer);
35-
# In practice in real systems one quickly ends up with cases
36-
# where it is unclear when to dispose of the object.
37-
# If you call the `dispose_of_some_object` function more
38-
# than once of too soon, you could easily crash the program.
39-
# CffiNativeHandle is designed to alleviate this headache by
40-
# using Matlab native reference counting of `handle` classes to reliably dispose of objects.
41-
42-
# This class is originally inspired from a class with a similar purpose in C#. See https://github.com/rdotnet/dynamic-interop-dll
43-
44-
def __init__(self, handle: "CffiData", type_id: str = None, prior_ref_count: int = 0):
65+
66+
def __init__(self, handle: "CffiData", type_id: Optional[str] = None, prior_ref_count: int = 0):
4567
"""Initialize a reference counter for a resource handle, with an initial reference count.
4668
4769
Args:
@@ -195,7 +217,7 @@ def __init__(
195217
self,
196218
handle: "CffiData",
197219
release_native: Optional[Callable[["CffiData"], None]],
198-
type_id: str = None,
220+
type_id: Optional[str] = None,
199221
prior_ref_count: int = 0,
200222
):
201223
"""New reference counter for a CFFI resource handle.
@@ -381,7 +403,7 @@ def ptr(self) -> Any:
381403

382404
def wrap_as_pointer_handle(
383405
obj_wrapper: Any, stringent: bool = False
384-
) -> Union[CffiNativeHandle, OwningCffiNativeHandle, GenericWrapper, None]:
406+
) -> Union[CffiNativeHandle, OwningCffiNativeHandle, GenericWrapper]:
385407
"""Wrap an object, if need be, so that its C API pointer appears accessible via a 'ptr' property
386408
387409
Args:
@@ -397,7 +419,7 @@ def wrap_as_pointer_handle(
397419
# 2016-01-28 allowing null pointers, to unlock behavior of EstimateERRISParameters.
398420
# Reassess approach, even if other C API function will still catch the issue of null ptrs.
399421
if obj_wrapper is None:
400-
return None
422+
return GenericWrapper(None)
401423
# return GenericWrapper(FFI.NULL) # Ended with kernel crashes and API call return, but unclear why
402424
elif isinstance(obj_wrapper, CffiNativeHandle):
403425
return obj_wrapper

tests/test_native_handle.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,13 @@ def test_wrapper_helper_functions():
264264
def test_wrap_as_pointer_handle():
265265
pointer = ut_dll.create_dog()
266266
dog = wrap_cffi_native_handle(pointer, "dog", ut_dll.release)
267-
assert wrap_as_pointer_handle(None, False) is None
268-
assert wrap_as_pointer_handle(None, True) is None
267+
268+
# Allow passing None via a wrapper, to facilitate uniform code generation with c-api-wrapper-generation
269+
assert isinstance(wrap_as_pointer_handle(None, False), GenericWrapper)
270+
assert isinstance(wrap_as_pointer_handle(None, True), GenericWrapper)
271+
assert wrap_as_pointer_handle(None, True).ptr is None
272+
assert wrap_as_pointer_handle(None, False).ptr is None
273+
269274
x = ut_ffi.new("char[10]", init="foobarbaz0".encode('utf-8'))
270275
assert isinstance(wrap_as_pointer_handle(x, False), OwningCffiNativeHandle)
271276
assert isinstance(wrap_as_pointer_handle(x, True), OwningCffiNativeHandle)

0 commit comments

Comments
 (0)