--- Title: Python Enums, ctypes.Structures, and DLL exports Subtitle: Illustrating the Simplest Use of ctypes Structures Summary: > Unfortunately, the official docs for ctypes leaves a few things out—namely, the most basic use case with from_param! Here's a simple, working example from my own development work. Tags: [software development] Date: 2015-05-28 18:00 Slug: ctypes-structures-and-dll-exports ... For one of my contracts right now, I'm writing a `ctypes` Python interface to existing C code. I got stuck and confused for quite a while on getting the interface to a given function to build correctly, and along the way had to try to understand the `from_param` class method. The official docs are... fine... but the examples provided don't cover the most common/basic use case: defining a simple, *non-ctypes* data type as an argument to a DLL-exported function. Let's say you have a C function exported from a DLL; for convenience we'll make it something rather silly but easy to understand: ```c /** my_exported.h */ #include "exports.h" typedef enum { ZERO, ONE, TWO } MyEnum; MY_API int getAnEnumValue(MyEnum anEnum); ``` The implementation just gives back the integer value of the function: ```c int getAnEnumValue(MyEnum anEnum) { return (int)anEnum; } ``` As I said, a *very* silly example. Note that you don't technically need the `(int)` cast there; I've just put it in to be explicit about what we're doing. How would we use this from Python? Assuming we have a DLL named `my_dll` which exports the `getAnEnumValue` function, we'd load it up roughly like this:[^1] ```python import ctypes as c my_dll = c.cdll.LoadLibrary('my_dll') ``` Then, we bind to the function like this: ```python get_an_enum_value = my_dll.getAnEnumValue ``` Now, when you do this, you usually also supply the `argtypes` and `restype` values for these functions. If you're like me, you'd think, "Oh, an enum---a perfect opportunity to use the `Enum` type in Python 3.4+!" and then you'd do something like this: ```python import ctypes as c from enum import IntEnum class MyEnum(IntEnum): ZERO = 0 ONE = 1 TWO = 2 my_dll = c.cdll.LoadLibrary('my_dll') get_an_enum_value = my_dll.getAnEnumValue get_an_enum_value.argtypes = [MyEnum] get_an_enum_value.restype = c.c_int ``` That seems sensible enough, but as it is, it won't work: you'll get an error: ``` TypeError: item 1 in _argtypes_ has no from_param method ``` This is because `argtypes` values *have* to be either existing `ctypes` types[^2] or supply either: - a `from_param` classmethod, or - an `_as_parameter_` attribute You can use `ctypes.Structure` subclasses natively that way, because the `Structure` class supplies its `from_param` classmethod. The same is *not* true of our custom enum class, though. As the docs put it: > If you have defined your own classes which you pass to function calls, you > have to implement a `from_param()` class method for them to be able to use > them in the argtypes sequence. The `from_param()` class method receives the > Python object passed to the function call, it should do a typecheck or > whatever is needed to make sure this object is acceptable, and then return the > object itself, its `_as_parameter_` attribute, or whatever you want to pass as > the C function argument in this case. Again, the result should be an integer, > string, bytes, a `ctypes` instance, or an object with an `_as_parameter_` > attribute. So, to make the enum type work, we need to add a `from_param` class method or an `_as_parameter_` attribute to it. Thus, either of these options will work: ```python class MyEnum(IntEnum): ZERO = 0 ONE = 1 TWO = 2 # Option 1: set the _as_parameter value at construction. def __init__(self, value): self._as_parameter = int(value) # Option 2: define the class method `from_param`. @classmethod def from_param(cls, obj): return int(obj) ``` In the constructor-based option, the `value` argument to the constructor is the value of the `Enum` instance. Since the value of anan `IntEnum` is always the same as the integer to whcih it is bound, we can just return `int(value)`. The `from_param` approach works a little differently, but with the same results. The `obj` argument to the `from_param` method is the object instance, in this case the enumerated value itself. *Any* `Enum` with an integer value can be directly cast to `int` (though it is possible for `Enum` instances to have other values, so be careful), and since we have an `IntEnum` here, we can again just return `int(obj)` directly. Now, let's say we want to apply this pattern to more than a single `IntEnum` class, because our C code defines more than one enumeration. Extracting it to be common functionality is simple enough: just create a class that implements the class method, and inherit from it. ```python class CtypesEnum(IntEnum): """A ctypes-compatible IntEnum superclass.""" @classmethod def from_param(cls, obj): return int(obj) class MyEnum(CtypesEnum): ZERO = 0 ONE = 1 TWO = 2 ``` Our final (working!) Python code, then, would be: ```python # Import the standard library dependencies import ctypes as c from enum import IntEnum # Define the types we need. class CtypesEnum(IntEnum): """A ctypes-compatible IntEnum superclass.""" @classmethod def from_param(cls, obj): return int(obj) class MyEnum(CtypesEnum): ZERO = 0 ONE = 1 TWO = 2 # Load the DLL and configure the function call. my_dll = c.cdll.LoadLibrary('my_dll') get_an_enum_value = my_dll.getAnEnumValue get_an_enum_value.argtypes = [MyEnum] get_an_enum_value.restype = c.c_int # Demonstrate that it works. print(get_an_enum_value(MyEnum.TWO)) ``` The output will be `2`, just as you'd expect! An important note: The type definition we've provided here will work for `argtypes` or `restype` assignments, but *not* as one of the members of a custom `ctypes.Structure` type's `_fields_` value. (Discussing how you'd go about doing that is beyond the scope of this post; the most direct approach is just to use a `ctypes.c_int` and note that it is intended to be used with a given `IntEnum`/`CtypesEnum` type.) --- Thanks to [\@oluseyi] for being my [rubber ducky] while I was working this out earlier this week! [\@oluseyi]: https://alpha.app.net/oluseyi [rubber ducky]: http://en.wikipedia.org/wiki/Rubber_duck_debugging [^1]: I'm leaving out the part where we build the DLL, and also the part where we locate the DLL, and only using the Windows convention. If you're on a \*nix system, you should use `'my_dll.so'` instead, and in any case you need to make sure the DLL is available in the search path. [^2]: I *love* the redundancy of "`ctypes` types," don't you?