我正在对底层操作系统库进行大量 ctypes 调用。每当文档引用存储在.h
. 文件某处,因为我必须去追踪它,并找出实际值是什么,以便我可以将它传递给函数。
有没有办法.h
用 ctypes 加载文件并访问所有常量?
不。
的早期版本ctypes
带有一个名为 的模块codegenerator
,它可以解析头文件,以获取常量值并将原型转换为restype
/argtypes
声明。但是,据我所知,这从未完成,并且在包含在 stdlib 中之前已从包中删除。
您可以深入研究源代码并提取常量内容,同时跳过更复杂的原型内容。
但是,我通常这样做的方式是编写自己的生成器。
例如,将此脚本作为设置过程的一部分运行:
constants = {}
with open('foo.h') as infile:
for name, value in re.findall(r'#define\s+(\w+)\s+(.*)', infile):
try:
constants[name] = ast.literal_eval(value)
except Exception as e:
pass # maybe log something
with open('_foo_h.py', w) as outfile:
outfile.write(repr(constants))
然后foo.py
就可以了from _foo_h import *
。
为此编写一个完美的正则表达式非常困难,也许是不可能的;编写一个适用于给定项目中您真正关心的标题的标题非常容易。实际上,通常,您只需要上面的那个,或者跳过评论的那个。
但有时这行不通。例如,头文件可能#define FOO_SIZE 8
用于 64 位构建和#define FOO_SIZE 4
32 位构建。你怎么处理?
为此,您要求编译器为您执行此操作。大多数编译器都有一种预处理文件的方法,该文件的预处理距离足以获得所有活动定义。一些编译器甚至可以以一种很好的格式转储宏定义,跳过其他所有内容。使用gcc
和标志兼容的编译器,例如clang
,-E
预处理和-dM
转储宏。所以:
macros = subprocess.check_output(['gcc', '-dM', '-E', '-', 'foo.h'])
for line in macros.splitlines():
try:
_, name, value = line.split(None, 2)
constants[name] = ast.literal_eval(value)
except Exception as e:
pass # again, do something nicer
您可能需要传入一些额外的编译器标志来控制正确定义的内容,例如pkgconfig foo --cflags
.
这还将为您提供在foo.h
(递归)包含的任何内容中定义的宏,以及 gcc 的内置宏。您可能想要也可能不想要其中的每一个。在69105 gcc flags中的某个地方,我相信有办法控制它,但我不记得它们了。
请注意,这些都不会为您提供常量变量或枚举,例如:
static const int SPAM_SPAM_SPAM = 73;
enum {
kSPAM = 1,
kEGGS
};
解析变得更加困难;您可能想要使用真正的 C99 解析器pycparser
,例如 — 或者,您想要解析类似gccxml
而不是gcc -E
. kEGGS
但即使没有你写一点逻辑,也不会告诉你这是 2。
如果你想处理 C++,那就更糟了,用 constexpr 和静态类成员以及用户定义的文字......</p>
或者……你必须使用ctypes
吗?
CFFI
提供了一种从 Python 调用 C 代码的不同方法——它使这变得容易得多。
Cython
允许您编写几乎是 Python 的代码,这些代码被编译为 C 语言,然后被编译为 Python 扩展模块,并且它可以直接包含头文件。
还有各种绑定生成器(例如,SWIG)或绑定编写库(例如,boost::python)可以更轻松地通过扩展模块将值导出到 Python。