如何将带有宏的头文件预处理为非宏头文件

How do I preprocess a header file with macros to a non macro header file

这里是C新手。我的用例是将 here 发布的头文件提供给 python 库 cffi,以便我可以绑定到 C 库。前面link中的头文件有宏。 cffi 我认为只接受没有宏的头文件。

重现步骤:

import cffi
import pathlib

ffi = cffi.FFI()
this_dir = pathlib.Path().absolute()
h_file_name = this_dir / "ddlog-processed.h"
with open(h_file_name) as h_file:
    ffi.cdef(h_file.read())

它给出错误:

Traceback (most recent call last):
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/cffi/cparser.py", line 305, in _parse
    ast = _get_parser().parse(fullcsource)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/pycparser/c_parser.py", line 152, in parse
    debug=debuglevel)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/pycparser/ply/yacc.py", line 331, in parse
    return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/pycparser/ply/yacc.py", line 1199, in parseopt_notrack
    tok = call_errorfunc(self.errorfunc, errtoken, self)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/pycparser/ply/yacc.py", line 193, in call_errorfunc
    r = errorfunc(token)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/pycparser/c_parser.py", line 1861, in p_error
    column=self.clex.find_tok_column(p)))
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/pycparser/plyparser.py", line 67, in _parse_error
    raise ParseError("%s: %s" % (coord, msg))
pycparser.plyparser.ParseError: /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/sys/_types/_int8_t.h:30:18: before: char

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/bin/invoke", line 10, in <module>
    sys.exit(program.run())
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/invoke/program.py", line 384, in run
    self.execute()
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/invoke/program.py", line 566, in execute
    executor.execute(*self.tasks)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/invoke/executor.py", line 129, in execute
    result = call.task(*args, **call.kwargs)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/invoke/tasks.py", line 127, in __call__
    result = self.body(*args, **kwargs)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/tasks.py", line 48, in build_cffi
    ffi.cdef(h_file.read())
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/cffi/api.py", line 112, in cdef
    self._cdef(csource, override=override, packed=packed, pack=pack)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/cffi/api.py", line 126, in _cdef
    self._parser.parse(csource, override=override, **options)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/cffi/cparser.py", line 358, in parse
    self._internal_parse(csource)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/cffi/cparser.py", line 363, in _internal_parse
    ast, macros, csource = self._parse(csource)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/cffi/cparser.py", line 307, in _parse
    self.convert_pycparser_error(e, csource)
  File "/Users/rabraham/Documents/dev/ddlog/playpen_ddlog/venv/lib/python3.7/site-packages/cffi/cparser.py", line 336, in convert_pycparser_error
    raise CDefError(msg)
cffi.CDefError: parse error
/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/sys/_types/_int8_t.h:30:18: before: char

使用 CFFI 有两个步骤。第一个是决定您是使用 ABI 还是 API 模式---ffi.dlopen()ffi.set_source();有关详细信息,请参阅 https://cffi.readthedocs.io/en/latest/overview.html#abi-versus-api。然后你需要相应地编写对 ffi.cdef() 的调用。我通常建议使用 API 模式,这种模式更加灵活,但需要 C 编译器 install-time (就像你编写标准 CPython C 扩展模块一样)。在这两种模式下,您应该在对 ffi.cdef() 的调用中手动复制 C header 的部分;在 API 模式下,您可以省略更多细节并用 ... (dot-dot-dot).

替换它们

在这两种模式中,您都不能随意粘贴一个 C header 来利用所有标准 C 功能。调用 gcc -E 只会让问题变得更难(但在某些情况下已经完成了非常大的库,有很多自定义 post-processing)。 ffi.cdef() 的要点是,通常情况下,您粘贴 简化 版本只是 您需要的功能。

准确回答您的问题:

  • 直接支持只是常量的宏,如整数。在 API 模式下你也可以写 #define MY_CONSTANT ... 和 dot-dot-dot.

  • 在 API 模式(仅)中支持像函数一样工作的宏,方法是使用以下技巧:将它们写在 ffi.cdef() 中,就好像它们是常规函数一样。这足以调用它们了。

  • 对于更复杂的情况,您可能必须编写一个真正的 C 函数来根据需要包装一个或多个宏的使用。随便起一个新的函数名,在ffi.cdef()声明函数,最后在ffi.set_source().

  • 直接实现函数

在ABI模式下,完全不支持后两种情况---ABI中不再存在宏。