【问题标题】:Can python load definitions from a C header file?python可以从C头文件中加载定义吗?
【发布时间】:2020-03-03 02:20:55
【问题描述】:

我正在围绕 C API 编写一个 python Wrapper。我有一个广泛的 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,但是直接从头文件加载它的定义或者直接将它作为myAPI 的属性会很方便。这可能吗?

【问题讨论】:

标签: python ctypes header-files


【解决方案1】:

这是可能的。几年前,我编写了一个工具来使用 pyparsing 扫描文件以查找 C++ enum 语法。现在是pyparsing example,我在这里复制了它,以防链接发生变化。如您所见,该文件甚至不必是完全有效的 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

【讨论】:

    【解决方案2】:

    改编 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类型(注意这是when subclassing EnumMeta is a good idea)的一次:

    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 CPPEnum(IntEnum, metaclass=CPPEnumType):
        pass
    

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

    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 Enumenum34 backportAdvanced Enumeration (aenum) 库的作者。

    【讨论】:

    • 谢谢。这个答案增加了很多价值!我不确定是否应该将我接受的答案更改为这个答案,因为我最初的问题基本上已经回答了。但是非常感谢您的回答!
    • @Dschoni:我很高兴它有用!
    猜你喜欢
    • 1970-01-01
    • 2014-03-24
    • 2013-12-15
    • 2020-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多