How do I correctly call a function that takes a "custom enum" as argument using ctypes and ctypes based enums?

Viewed 417

I really hope some Python/Ctypes/C expert can help me with this one, it is probably my lack of knowledge on the typing structure to correctly use Ctypes when using Python to interact with C libraries.

Objective: I need to access few library functions loading a DLL using ctypes and interacting with it. The idea works fine most of the time, but there are few functions that take enums as parameters and those enums are very sensitive when it comes to int type. Here is a fake example:

typedef enum led_property : uint8_t {
  LED_OFF = 0
  LED_POWER
}

int32_t configure_led(const led_property, const int32_t value)

This is the kind of enum the functions receive, not only for uint8_t, but also for int32_t, int_64t and so on.

From a python recipe I found online, I managed to "adapt" python enums with ctypes types:

class EnumerationTypeUInt8(type(c_uint8)):
def __new__(metacls, name, bases, dict):
    if not "_members_" in dict:
        _members_ = {}
        for key, value in dict.items():
            if not key.startswith("_"):
                _members_[key] = value
        dict["_members_"] = _members_
    cls = type(c_uint8).__new__(metacls, name, bases, dict)
    for key, value in cls._members_.items():
        globals()[key] = value
    return cls

def __contains__(self, value):
    return value in self._members_.values()

def __repr__(self):
    return "<Enumeration {}>".format(self.__name__)

def EnumerationUInt8(c_uint8):
__metaclass__ = EnumerationTypeUInt8
_members_ = {}

def __init__(self, value):
    for k, v in self._members_.items():
        if v == value:
            self.name = k
            break
    else:
        raise ValueError("No enumeration member with value {}".format(value))
    c_uint8.__init__(self, value)

@classmethod
def from_param(cls, param):
    if isinstance(param, EnumerationUInt8):
        if param.__class__ != cls:
            raise ValueError("Can not mix enumeration members")
        else:
            return param
    else:
        return cls(param)

def __repr__(self):
    return "<member {}={} of {}".format(self.name, self.value, self.__class__)

I'm loading the library and decorating its functions as follows:

class LedProperty(EnumerationUInt8):
   LED_OFF = c_uint8(0)
   LED_POWER = c_uint8(1)

lib = "library.dll"
self._lib = CDLL(lib)
configure_led = self._lib.configure_led
configure_led.argtypes = [LedProperty, c_int32]
configre_led.restype = c_int32

The problem is that I tried all that I could and I can never call that configure_led python function properly, most of the time I get the following error:

ctypes.ArgumentError class 'ValueError' No enumeration member with value c_ubyte(1)
or
ctypes.ArgumentError class 'ValueError' No enumeration member with value 1

That is happening because I can see when debugging that the "EnumerationUInt8" "self.members.items()" is always an empty dict. So probably this custom enums with ctypes are not loading their members properly. I'm always ending on that "else: return cls(param)"

Tried:

configure_led(LedProperty.LED_POWER, 5)
configure_led(LedProperty.LED_POWER.value, 5)
configure_led(c_uint8(LedProperty.LED_POWER), 5)

... and so on! Nothing seems to be correct.

Does anyone knows how to properly declare Enums using cytpes types and later use those enums as arguments for functions?

Obs.: I'm currently using Python 3.8.3 Thanks in advance!

1 Answers

Assuming this implementation, test.cpp:

#include <stdint.h>

enum led_property : uint8_t {
    LED_OFF = 0,
    LED_POWER
};

extern "C" __declspec(dllexport) int32_t configure_led(enum led_property prop, int32_t value) {
    return prop * value;
}

This will allow only LED values for the first parameter:

from ctypes import *
from enum import Enum,auto

class LED(Enum):

    OFF = 0
    POWER = auto()  # autoincrement from last value

    @classmethod
    def from_param(cls,obj):
        if not isinstance(obj,LED):
            raise TypeError('not an LED enumeration')
        return c_int8(obj.value)

dll = CDLL('./test')
dll.configure_led.argtypes = LED,c_int32
dll.configure_led.restype = c_int32

print(dll.configure_led(LED.OFF,5))   # prints 0
print(dll.configure_led(LED.POWER,5)) # prints 5
print(dll.configure_led(0,5))         # not an LED enumeration
Related