Search code examples
pythonerror-handlingctypes

Checking the return value of API-Functions Python


I am using an C++ API via Python using ctypes.CDLL:

api = CDLL(f'{path}/dll_name.dll')

This api has functions like open, remove, delete, select, read, write, etc... Whenever I use these functions it returns something. When I call these api functions via debugger one by one, I can of course see the return value.

However, since the return value of these functions is just some information of what happened after calling this function, it is not really necessary to save it in some variable, I simply call them and proceed.

Return values are integers between 0 to 15;

0: everything okay
1: license missing
2: wrong name
...

I would like to call 3 of these functions one after one with also checking if their return value is 0, otherwise raise some error. For that, the simple way would be something following, however it is also not that elegant, since I have to rewrite the same thing lots of time...:

status = api.select('something')
if not status:
    pass
else:
    raise SomeError(status)

status = api.open('something')
if not status:
    pass
else:
    raise SomeError(status)

...

Is there a way to handle this in a better way?


Solution

  • ctypes functions can assign a callable as an errcheck attribute that will post-process the return value:

    errcheck

    • Assign a Python function or another callable to this attribute. The callable will be called with three or more arguments:

    • callable(result, func, arguments)

      • result is what the foreign function returns, as specified by the restype attribute.

      • func is the foreign function object itself, this allows reusing the same callable object to check or post process the results of several functions.

      • arguments is a tuple containing the parameters originally passed to the function call, this allows specializing the behavior on the arguments used.

    • The object that this function returns will be returned from the foreign function call, but it can also check the result value and raise an exception if the foreign function call failed.

    Below is a simple C function that returns the error code passed to it, and a Python example that uses error check to raise exceptions if the return value is non-zero.

    test.c

    #ifdef _WIN32
    #   define API __declspec(dllexport)
    #else
    #   define API
    #endif
    
    API int func(int retval) {
        return retval;
    }
    

    test.py

    import ctypes as ct
    
    class FuncError(Exception): ...
    class LicenseMissingError(FuncError): ...
    class WrongNameError(FuncError): ...
    
    def validate(result, func, arguments):
        # requires Python 3.10 for match statement, use if/elif/else otherwise
        match result:
            case 0: return None # no error
            case 1: raise LicenseMissingError()
            case 2: raise WrongNameError()
            case _: raise FuncError(result)
    
    dll = ct.CDLL('./test')
    dll.func.errcheck = validate
    
    for i in range(4):
        try:
            print(dll.func(i))
        except FuncError as e:
            print(type(e), e)
    

    Output:

    None
    <class '__main__.LicenseMissingError'>
    <class '__main__.WrongNameError'>
    <class '__main__.FuncError'> 3
    

    Your example could look like the following, where any error would stop execution by raising an exception or use try/except to handle the exception gracefully in one place.

    api = CDLL(f'{path}/dll_name.dll')
    api.select.errcheck = validate
    api.open.errcheck = validate
    
    api.select('something')
    api.open('something')