5

I have an enumeration data type in C. How should I declare that in python-ctypes? I want this enum variable to be part of a structure and the assignment of the values to this structure would be done through memmove. After assigning, I want to display the values of each variables in the structure, and for the enum types I want to display the enum-string.

Raj Kumar
  • 143
  • 2
  • 11
  • 1
    What class, exactly, are you referring to? – Makoto Feb 09 '15 at 15:51
  • Yes, it is. https://docs.python.org/2/library/ctypes.html Last time I checket, addressof was not C ;) – deets Feb 09 '15 at 15:51
  • "header" is an instance of Ctypes-Structure. I want to know which method of the class that "header" is an instance of, will be called when I try to assign the variables of the "header"-instance, via memmove. From the term memmove, I can guess that it has to be a memory copy and it might not even know where this copy is being done. And hence, no method of the Ctypes-Structure class will be called. Is that correct? – Raj Kumar Feb 09 '15 at 15:59
  • Ok, to explain my problem little more, I'm having some enum variables inside my ctype-structures. Since, enumeration is not supported by ctypes, I got a piece of code from http://code.activestate.com/recipes/576415-ctype-enumeration-class/ . The problem with this part is, since I'm doing a memmove of data over the structure this enum belogs to, the __init__ method of class Enumeration(c_uint) is not at all called. So, I'm unable to get the enum definition of the value that is assigned to now. – Raj Kumar Feb 09 '15 at 16:04
  • Correct - this is directly replacing some memory "behind the curtains". So you need to declare some auxiliary methods on your struct that perform the necessary operations after the memmove. – deets Feb 09 '15 at 16:29
  • Hmm thanks :-) Is it possible to find how this part is done for other data-types? For ex, what would have happened to the c_uint variables that i have in the structure? How is that, they are giving me the correct number? For this enum-object, if I can get the value that is assigned to it, then I can get the corresponding string. Here is how my structure looks like class headerStruct(Structure): _fields_ = [("param1", EBoolean),("param2", c_uint)]. When i do a getattr for param2, I'm getting the correct integer that is copied. But the same is not happening for param1. – Raj Kumar Feb 09 '15 at 16:39
  • You mean, I cannot use that? – Raj Kumar Feb 09 '15 at 18:00
  • 1
    @RajKumar: you may want to [edit] your question now that it has become clear what actual problem you were facing. – Martijn Pieters Feb 09 '15 at 18:47
  • @ Martijn Pieters: Sure. Changing it now. – Raj Kumar Feb 09 '15 at 19:01

3 Answers3

10

The Enumeration class suggested by Raj Kumar was broken in that it required the __init__ to be run to set a new value in a variable, and thus unusable if the value was changed on C side. Here is a fixed version thereof:

class EnumerationType(type(c_uint)):
    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_
        else:
            _members_ = dict["_members_"]

        dict["_reverse_map_"] = { v: k for k, v in _members_.items() }
        cls = type(c_uint).__new__(metacls, name, bases, dict)
        for key,value in cls._members_.items():
            globals()[key] = value
        return cls

    def __repr__(self):
        return "<Enumeration %s>" % self.__name__

class CEnumeration(c_uint):
    __metaclass__ = EnumerationType
    _members_     = {}

    def __repr__(self):
        value = self.value
        return "<%s.%s: %d>" % (
            self.__class__.__name__,
            self._reverse_map_.get(value, '(unknown)'),
            value
        )

    def __eq__(self, other):
        if isinstance(other, (int, long)):
            return self.value == other

        return type(self) == type(other) and self.value == other.value

Now one can declare a CEnumeration:

class EBoolean(CEnumeration):
    FALSE = 0
    TRUE = 1

and use it:

class HeaderStruct(Structure):
    _fields_ = [("param1", EBoolean), 
                ("param2", c_uint)]

Examples:

>>> header = HeaderStruct()
>>> header.param1
<EBoolean.FALSE: 0>
>>> memmove(addressof(header), b'\x01', 1)  # write LSB 0x01 in the boolean
>>> header.param1
<EBoolean.TRUE: 1>
>>> header.param1 == EBoolean.TRUE
True
>>> header.param1 == 1   # as a special case compare against ints
True
>>> header.param1.value
1L
  • Hi Thanks for the answer. But can you please explain it a bit more?? As of now, the only way for assigning this variable is via memmove. And after I do this, I'm able to read the value assigned to other 'c_uint' variables in the structures using getattr(). I'm not sure of what method to call in this object to get the value assigned to this variable in the ctypes-structure. – Raj Kumar Feb 09 '15 at 16:52
  • Thanks a ton.. This is precisely what I wanted. Thanks again for the time you spent in correcting this :-) – Raj Kumar Feb 09 '15 at 18:55
  • Instead of calling `memmove` you can usually use `header = HeaderStruct.from_buffer(readBuffer)`, which avoids the copy if the buffer is writable. For a copy (or for a read-only buffer) use `HeaderStruct.from_buffer_copy(readBuffer)`. – Eryk Sun Feb 09 '15 at 20:37
  • The above comment isn't directed at you, per se. I upvoted your answer. I wish the enum module of 3.x made this easier, but `enum.EnumMeta` leads to a metaclass conflict. – Eryk Sun Feb 10 '15 at 10:54
  • No offence was taken. I did spend quite a time with the enum34 on python 2.7 trying to get it to work properly, but I reached the same conclusion: one would have to fork the whole module to make it work with `PyCSimpleType`, which would have made too long an answer. – Antti Haapala -- Слава Україні Feb 10 '15 at 11:09
  • Just a minor issue that should be noted, but `globals()` is not relative to the module the `CEnumeration` subclass (eg `EBoolean`) is defined in, but rather the module `EnumerationType` is defined in... All entry definitions in any subclasses will be defined there. – Tcll Mar 04 '19 at 13:19
  • please consider using `sys._getframe(1).f_globals.update(_members_)` (and remember to delete the frame), or use inspect for a more pythonic approach. – Tcll Mar 04 '19 at 13:36
3

Antti Haapala did a fantastic job answering! I, however, did run into some minor issues when using it with Python 3.2.2 that I believe are worth noting. Instead of:

class CEnumeration(c_uint):
    __metaclass__ = EnumerationType
    _members_     = {}

You need to do:

class CEnumeration(c_uint, metaclass = EnumerationType):
    _members_     = {}

Also, int and long have been unified in Python 3 so:

def __eq__(self, other):
        if isinstance(other, (int, long)):
            return self.value == other

        return type(self) == type(other) and self.value == other.value

Becomes:

def __eq__(self, other):
        if isinstance(other, int):
            return self.value == other

        return type(self) == type(other) and self.value == other.value
Tigger
  • 63
  • 3
2

Here is an extension of the solution from Antti Happala, using the modifications for Python 3 as suggested by Tigger, plus an exentension for arbitrary ctypes as base class (e.g. uint8 vs. uint16):

from ctypes import *


def TypedEnumerationType(tp):
    class EnumerationType(type(tp)):  # type: ignore
        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_
            else:
                _members_ = dict["_members_"]

            dict["_reverse_map_"] = {v: k for k, v in _members_.items()}
            cls = type(tp).__new__(metacls, name, bases, dict)
            for key, value in cls._members_.items():
                globals()[key] = value
            return cls

        def __repr__(self):
            return "<Enumeration %s>" % self.__name__

    return EnumerationType


def TypedCEnumeration(tp):
    class CEnumeration(tp, metaclass=TypedEnumerationType(tp)):
        _members_ = {}

        def __repr__(self):
            value = self.value
            return f"<{self.__class__.__name__}.{self._reverse_map_.get(value, '(unknown)')}: {value}>"

        def __eq__(self, other):
            if isinstance(other, int):
                return self.value == other

            return type(self) == type(other) and self.value == other.value

    return CEnumeration

Here is a small unit test for this, showing that it actually works to differentiate between unit8 and uint16 enums:

class Foo(TypedCEnumeration(c_uint16)):
        A = 42
        B = 1337

    class Bar(TypedCEnumeration(c_uint8)):
        A = 5
        B = 23

    assert isinstance(Foo(Foo.A), c_uint16)
    assert isinstance(Bar(Bar.A), c_uint8)

    assert type(Foo.A) == int
    assert Foo.A == 42
    assert str(Foo(Foo.A)) == "<Foo.A: 42>"
    assert str(Bar(Bar.B)) == "<Bar.B: 23>"

    class FooBar(Structure):
        _pack_ = 1
        _fields_ = [("foo", Foo), ("bar", Bar)]

    foobar = FooBar(Foo.A, Bar.B)

    assert sizeof(foobar) == 3
    assert foobar.foo.value == 42
    assert foobar.bar.value == 23

    assert [int(x) for x in bytes(foobar)] == [42, 0, 23]
Patrick Roocks
  • 3,129
  • 3
  • 14
  • 28