Dsc*_*oni 6 python ctypes header-files
我正在围绕 C API 编写一个 python 包装器。我有一个广泛的 API 描述,现在我正在努力实现头文件中定义的枚举。
假设我内部有一个 C API 函数myAPI.dll
,它接受一个枚举作为参数,例如:
void SomeFunction(SomeEnum data)
Run Code Online (Sandbox Code Playgroud)
从头文件中,我可以看到SomeEnum
如下所示:
enum SomeEnum{
SomeValue = 1,
SomeOtherValue = 2,
SomeVeryStupidValue = -1
};
Run Code Online (Sandbox Code Playgroud)
在 python 中,我加载.dll
类似:
myAPI = ctypes.cdll.LoadLibrary('myAPI.dll')
Run Code Online (Sandbox Code Playgroud)
现在我希望能够调用:
myAPI.SomeFunction(SomeValue)
Run Code Online (Sandbox Code Playgroud)
我知道,我可以SomeValue
在 python 中定义,但是直接从头文件加载它的定义或者直接将它作为myAPI
. 这可能吗?
这是可能的。几年前我编写了一个工具,使用pyparsing扫描文件中的 C++enum
语法。现在它是一个pyparsing 示例,我在此处复制了它,以防链接发生更改。正如您所看到的,该文件甚至不必是完全有效的 C++ 文件。它定义语法并扫描文件以查找与语法匹配的文本,从而生成 Python 变量。enum
#
# 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
Run Code Online (Sandbox Code Playgroud)
输出:
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
Run Code Online (Sandbox Code Playgroud)
改编 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
Run Code Online (Sandbox Code Playgroud)
第一步是创建一个新EnumMeta
类型(请注意,这是子类化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,
)
Run Code Online (Sandbox Code Playgroud)
创建新类型后,我们可以创建新的基类:
class CPPEnum(IntEnum, metaclass=CPPEnumType):
pass
Run Code Online (Sandbox Code Playgroud)
一旦有了新的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)
Run Code Online (Sandbox Code Playgroud)
并在使用中:
>>> list(Hello)
[
<Hello.ZERO: 0>, <Hello.ONE: 1>, <Hello.TWO: 2>, <Hello.THREE: 3>,
<Hello.FIVE: 5>, <Hello.SIX: 6>, <Hello.TEN: 10>,
]
Run Code Online (Sandbox Code Playgroud)
声明:我是Python stdlibEnum
、enum34
backport和Advanced Enumeration ( aenum
)库的作者 。