python 可以从 C 头文件加载定义吗?

Can python load definitions from a C header file?

我正在写一个 python 围绕 C API 的包装器。我有一个广泛的 API 描述,现在我正在努力实现头文件中定义的枚举。

假设我在 myAPI.dll 中有一个 C API 函数,它接受一个枚举作为参数,例如:

void SomeFunction(SomeEnum data)

从头文件中,我可以看到SomeEnum看起来像:

enum SomeEnum{
    SomeValue = 1,
    SomeOtherValue = 2,
    SomeVeryStupidValue = -1
};

在 python 中,我加载 .dll 如下:

myAPI = ctypes.cdll.LoadLibrary('myAPI.dll')

现在我希望能够打电话给:

myAPI.SomeFunction(SomeValue)

我知道,我可以在 python 中定义 SomeValue,但直接从头文件加载它的定义或直接将其作为 [=19= 的属性会很方便].这可能吗?

有可能。几年前我写了一个工具来使用 pyparsing. It's now a pyparsing example 扫描文件的 C++ enum 语法,我在此处重现以防 link 发生变化。如您所见,该文件甚至不必是完全有效的 C++。它定义 enum 语法并扫描文件以查找与语法匹配的文本,生成 Python 个变量。

#
# cpp_enum_parser.py
#
# Posted by Mark Tolonen on comp.lang.python in August, 2009,
# Used with permission.
#
# Parser that scans through C or C++ code for enum definitions, and
# generates corresponding Python constant definitions.
#
#

from pyparsing import *

# sample string with enums and other stuff
sample = """
    stuff before
    enum hello {
        Zero,
        One,
        Two,
        Three,
        Five=5,
        Six,
        Ten=10
        };
    in the middle
    enum blah
        {
        alpha,
        beta,
        gamma = 10 ,
        zeta = 50
        };
    at the end
    """

# syntax we don't want to see in the final parse tree
LBRACE, RBRACE, EQ, COMMA = map(Suppress, "{}=,")
_enum = Suppress("enum")
identifier = Word(alphas, alphanums + "_")
integer = Word(nums)
enumValue = Group(identifier("name") + Optional(EQ + integer("value")))
enumList = Group(enumValue + ZeroOrMore(COMMA + enumValue))
enum = _enum + identifier("enum") + LBRACE + enumList("names") + RBRACE

# find instances of enums ignoring other syntax
for item, start, stop in enum.scanString(sample):
    id = 0
    for entry in item.names:
        if entry.value != "":
            id = int(entry.value)
        print("%s_%s = %d" % (item.enum.upper(), entry.name.upper(), id))
        id += 1

输出:

HELLO_ZERO = 0
HELLO_ONE = 1
HELLO_TWO = 2
HELLO_THREE = 3
HELLO_FIVE = 5
HELLO_SIX = 6
HELLO_TEN = 10
BLAH_ALPHA = 0
BLAH_BETA = 1
BLAH_GAMMA = 10
BLAH_ZETA = 50

调整 Mark Tolonen 的代码以创建实际的 Python 枚举:

# import the dependencies
from enum import EnumMeta, IntEnum
from pyparsing import Group, Optional, Suppress, Word, ZeroOrMore
from pyparsing import alphas, alphanums, nums

第一步是创建一个新的EnumMeta类型(注意这是一次:

CPPEnum = None
class CPPEnumType(EnumMeta):
    #
    @classmethod
    def __prepare__(metacls, clsname, bases, **kwds):
        # return a standard dictionary for the initial processing
        return {}
    #
    def __init__(clsname, *args , **kwds):
        super(CPPEnumType, clsname).__init__(*args)
    #
    def __new__(metacls, clsname, bases, clsdict, **kwds):
        if CPPEnum is None:
            # first time through, ignore the rest
            enum_dict = super(CPPEnumType, metacls).__prepare__(
                    clsname, bases, **kwds
                    )
            enum_dict.update(clsdict)
            return super(CPPEnumType, metacls).__new__(
                    metacls, clsname, bases, enum_dict, **kwds,
                    )
        members = []
        #
        # remove _file and _name using `pop()` as they will
        # cause problems in EnumMeta
        try:
            file = clsdict.pop('_file')
        except KeyError:
            raise TypeError('_file not specified')
        cpp_enum_name = clsdict.pop('_name', clsname.lower())
        with open(file) as fh:
            file_contents = fh.read()
        #
        # syntax we don't want to see in the final parse tree
        LBRACE, RBRACE, EQ, COMMA = map(Suppress, "{}=,")
        _enum = Suppress("enum")
        identifier = Word(alphas, alphanums + "_")
        integer = Word(nums)
        enumValue = Group(identifier("name") + Optional(EQ + integer("value")))
        enumList = Group(enumValue + ZeroOrMore(COMMA + enumValue))
        enum = _enum + identifier("enum") + LBRACE + enumList("names") + RBRACE
        #
        # find the cpp_enum_name ignoring other syntax and other enums
        for item, start, stop in enum.scanString(file_contents):
            if item.enum != cpp_enum_name:
                continue
            id = 0
            for entry in item.names:
                if entry.value != "":
                    id = int(entry.value)
                members.append((entry.name.upper(), id))
                id += 1
        #
        # get the real EnumDict
        enum_dict = super(CPPEnumType, metacls).__prepare__(clsname, bases, **kwds)
        # transfer the original dict content, names starting with '_' first
        items = list(clsdict.items())
        items.sort(key=lambda p: (0 if p[0][0] == '_' else 1, p))
        for name, value in items:
            enum_dict[name] = value
        # add the members
        for name, value in members:
            enum_dict[name] = value
        return super(CPPEnumType, metacls).__new__(
                metacls, clsname, bases, enum_dict, **kwds,
                )

创建新类型后,我们可以创建新的基class:

class CPPEnum(IntEnum, metaclass=CPPEnumType):
    pass

一旦你有了新的 CPPEnum 基础 class,使用它就很简单:

class Hello(CPPEnum):
    _file = 'some_header.h'

class Blah(CPPEnum):
    _file = 'some_header.h'
    _name = 'blah'              # in case the name in the file is not the lower-cased
                                # version of the Enum class name (so not needed in
                                # in this case)

并在使用中:

>>> list(Hello)
[
    <Hello.ZERO: 0>, <Hello.ONE: 1>, <Hello.TWO: 2>, <Hello.THREE: 3>,
    <Hello.FIVE: 5>, <Hello.SIX: 6>, <Hello.TEN: 10>,
    ]

披露:我是 Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) 库的作者。