如何用ctypes及基于ctypes的枚举正确调用含自定义枚举参数的函数?
It looks like your main issues stem from outdated Python 2-style metaclass syntax and missteps in how your enumeration class collects and handles members. Let's fix this with a clean, Python 3-compatible approach that works for any underlying ctypes integer type (uint8_t, int32_t, etc.).
First, let's break down what was wrong with your original code:
- Python 2 metaclass syntax: You used
__metaclass__ = EnumerationTypeUInt8, which doesn't work in Python 3. Metaclasses in Python 3 require themetaclasskeyword argument in the class definition. - Incorrect member initialization: Assigning
LED_OFF = c_uint8(0)instead of raw integers complicated member detection in your metaclass. - Broken member collection: The structure of your
EnumerationUInt8function prevented the metaclass from properly collecting enum members.
Step 1: Create a Generic Enumeration Metaclass
This metaclass will handle collecting enum members, setting up value-to-name mappings, and initializing the class properly for any ctypes integer type.
import ctypes from ctypes import c_uint8, c_int32, c_int64, CDLL class EnumMeta(type(ctypes._SimpleCData)): def __new__(cls, name, bases, namespace): # Collect all non-private, non-callable enum members members = {} for key, value in namespace.items(): if not key.startswith('_') and not callable(value): members[key] = value # Create the enum class and attach member mappings enum_class = super().__new__(cls, name, bases, namespace) enum_class._members_ = members enum_class._value_to_name_ = {v: k for k, v in members.items()} # Add each member as a pre-initialized enum instance on the class for key, value in members.items(): setattr(enum_class, key, enum_class(value)) return enum_class def __contains__(cls, value): return value in cls._members_.values() def __repr__(cls): return f"<Enumeration {cls.__name__}>"
Step 2: Build a Generic Enum Base Class
This factory function generates enum base classes tied to specific ctypes integer types, letting you handle uint8_t, int32_t, and other enum variants with the same pattern.
def create_enum_base(ctypes_type): class EnumBase(ctypes_type, metaclass=EnumMeta): def __init__(self, value): # Validate the input is a valid enum value if value not in self._value_to_name_: raise ValueError(f"No enumeration member with value {value}") super().__init__(value) self.name = self._value_to_name_[value] @classmethod def from_param(cls, param): # Auto-convert valid inputs to the enum type if isinstance(param, cls): return param elif isinstance(param, ctypes_type): return cls(param.value) elif isinstance(param, int): return cls(param) else: raise TypeError(f"Cannot convert {type(param)} to {cls.__name__}") def __repr__(self): return f"<member {self.name}={self.value} of {self.__class__.__name__}>" return EnumBase
Step 3: Define Your Specific Enums
Create enum classes for your C enums in one line each:
# Create base classes for different underlying types UInt8Enum = create_enum_base(c_uint8) Int32Enum = create_enum_base(c_int32) # Define your LED property enum (matches the C enum's uint8_t base) class LedProperty(UInt8Enum): LED_OFF = 0 LED_POWER = 1
Step 4: Load the DLL and Call the Function
Now you can properly configure and invoke your C function without errors:
# Load the target DLL lib = CDLL("library.dll") # Configure the function's argument types and return type configure_led = lib.configure_led configure_led.argtypes = [LedProperty, c_int32] configure_led.restype = c_int32 # Call the function (multiple valid approaches) result = configure_led(LedProperty.LED_POWER, 1) # Or pass a raw integer (from_param handles conversion) result = configure_led(1, 1) # Or pass a c_uint8 instance result = configure_led(c_uint8(1), 1)
Key Details
- Metaclass Automation: The
EnumMetametaclass automatically collects enum members when you define the class, and sets up reverse mappings for validation. - Flexible Type Support:
create_enum_baselets you generate enums for any ctypes integer type, so you can handle all your C enum variants consistently. - Automatic Conversion: The
from_parammethod ensures ctypes receives the correct type, even if you pass a raw integer or ctypes instance instead of an enum member. - Validation: The enum constructor blocks invalid values, preventing bad arguments from reaching your C library.
内容的提问来源于stack exchange,提问作者Thales

