6

我正在对底层操作系统库进行大量 ctypes 调用。每当文档引用存储在.h. 文件某处,因为我必须去追踪它,并找出实际值是什么,以便我可以将它传递给函数。

有没有办法.h用 ctypes 加载文件并访问所有常量?

4

1 回答 1

6

不。

的早期版本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 432 位构建。你怎么处理?

为此,您要求编译器为您执行此操作。大多数编译器都有一种预处理文件的方法,该文件的预处理距离足以获得所有活动定义。一些编译器甚至可以以一种很好的格式转储宏定义,跳过其他所有内容。使用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。

于 2013-09-12T22:46:18.590 回答