2

I am trying to wrap to python3 a very simple Object Oriented C-API. This basically follow the same pattern as:

So basically my issue is as follow, I have the following input API:

$ cat api.h
/* ns::a */
typedef struct ns_a ns_a;
ns_a* ns_a_create();
void ns_a_delete(ns_a* a);
bool ns_a_execute(ns_a* a);
/* ns::b */
typedef struct ns_b ns_b;
ns_b* ns_b_create();
void ns_b_delete(ns_b* b);
bool ns_b_run(ns_b* b);

Which I then wrap using the following steps:

class _TypeSafeHandle:
    def __init__(self, ptr):
        self._as_parameter_ = ptr
    @classmethod
    def from_param(cls, obj):
        if obj.__class__ != cls:
            raise ValueError(f"Not a {obj.__class__.__name__} reference")
        if not obj._as_parameter_:
            raise ValueError("Passing undefined instance")
        return obj

@final
class _A(_TypeSafeHandle):
    """ for library author, not intended to be exposed publicly """
    def __init__(self, ptr):
        super().__init__(ptr)

@final
class _B(_TypeSafeHandle):
    def __init__(self, ptr):
        super().__init__(ptr)

Given the type definition I can now expose the C-API as:

# a
ns_a_create = _func('ns_a_create', c_void_p, None, _check_ns_a)
ns_a_delete = _func('ns_a_delete', None, [_A])
ns_a_execute = _func('ns_a_execute', c_bool, [_A])
# b
ns_b_create = _func('ns_b_create', c_void_p, None, _check_ns_b)
ns_b_delete = _func('ns_b_delete', None, [_B])
ns_b_run = _func('ns_b_run', c_bool, [_B])

Using:

def _func(name, restype, argtypes, errcheck):
    func = getattr(_lib, name)
    func.restype = restype
    func.argtypes = argtypes
    func.errcheck = errcheck
    return func

and:

def _check_ns_a(result, _func, _args):
    if result is None:
        raise MemoryError("internal memory allocation failure")
    if type(result) != int:
        raise TypeError("Expecting int value for pointer")
    return = _A(c_void_p(result))

and

def _check_ns_b(result, _func, _args):
    if result is None:
        raise MemoryError("internal memory allocation failure")
    if type(result) != int:
        raise TypeError("Expecting int value for pointer")
    return = _B(c_void_p(result))

At this step, I can now expose the class A to my user:

class A:
    def __init__(self):
        self._a = ns_a_create()
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        ns_a_delete(self._a)
    def execute(self):
        ret = ns_a_execute(self._a)
        if not ret:
            raise ValueError("Could not execute")

I'd like to keep the intermediate _A and _B class as implementation detail for the library author (avoid shooting in the foot passing wrong instance to wrong delete function). I find it more readable than declaring a forward declared class A:

class A:  # forward declaration
    pass
# _func declaration with type `A` instead of `_A`
class A:
  # actual implementation detail

So my question is: is there a way to reduce code redundancy in between _A and _B ? Is there a way to reduce code redundancy in between _check_ns_a and _check_ns_b ?

malat
  • 12,152
  • 13
  • 89
  • 158
  • 1
    The *ns\_\** structures definitions must match in the 2 languages (if you want to modify fields from *Python*, if it's just an opaque pointer, it's fine the way it is). Why all those small snippets instead all the code in the right order (which would also be a *MCVE*)? As for your question, some code can be dropped by adding new parameter(s) to different functions / methods. But everything seems a bit too complicated. – CristiFati Nov 15 '21 at 15:28
  • @CristiFati ... and so your suggested implementation is ... ? – malat Nov 19 '21 at 12:53
  • you could use something like [Cython](https://cython.org) to connect a C++ API to Python without intermediate wrapping in C. – tromgy Jan 17 '22 at 00:18
  • @tromgy the interface of the library is C. I have no real interest in writing intermediate c++ for accessing it via python. – malat Feb 01 '22 at 10:12
  • @malat, even better then. With Cython you can expose C or C++ API to Python. C should be easier. – tromgy Feb 01 '22 at 10:44

0 Answers0