【问题标题】:How can use CFFI to call an existing C function given the source code?给定源代码,如何使用 CFFI 调用现有的 C 函数?
【发布时间】:2016-10-22 12:10:37
【问题描述】:

我有一个 C 源代码/头文件,它是一个更大项目的一部分。我想将其作为一个单元进行测试,独立于实际项目。虽然可以通过使用不同的main() 在 C 中创建一个新项目来做到这一点,但我想看看我是否可以使用 Python (3) 及其框架(例如nose)来加速测试的构建,使用现有的报告框架等。

我的印象是我可以用 CFFI 做到这一点。这是一个示例 C 文件:

// magic.c
// Implementation of magic.
int add(int a, int b)
{
    return a;
}

标题:

// magic.h
// Add two numbers (where a + b is not greater than INT_MAX).
int add(int a, int b);

这是一个试图编译它的脚本,以便我可以调用一些函数:

# cffi_test.py
import cffi

INCLUDE_DIRS = ('.',)

SOURCES = ('magic.c',)

ffi = cffi.FFI()

ffi.set_source(
    '_magic_tests',
    '#include "magic.h"',
    include_dirs = INCLUDE_DIRS,
    sources = SOURCES,
    libraries = [],
    )

ffi.compile()

最终我计划在一组单元测试之前将其作为设置的一部分,例如。纯 Python 函数 test_add() 将通过在测试设置中构造的 ffi 对象调用并检查 C 函数 add() 的结果。

上面的脚本似乎可以工作;它运行没有错误,它创建一个_magic_tests.c 文件、一个_magic_tests.cp35-win32.pyd 文件和一个Release 目录。我也可以import _magic_tests 没有错误。

但我不知道如何通过 CFFI 实际调用 C 函数。我找不到set_source() 函数的任何文档,它似乎是整个过程不可或缺的一部分。 overview 提到了很多,但 reference 包含它的出现次数为零。文档 do 有一个关于 calling functions 的部分,但它引用了一些 lib 对象,但没有显示它是如何创建的。如果我查看前面的示例,有一个从 ffi.dlopen() 创建的 lib 对象,但我不知道如何将其应用于 CFFI 本身正在生成的东西。

我的大问题(即我的X problem)是:

  • CFFI 是否是用于以跨平台(Windows 7-10、Linux、OS X)方式调用和测试 C 函数的合理工具,如果是,如何使用?

我目前的方法(即我的Y problems)引起的问题是:

  • set_source() 的文档在哪里?我怎样才能知道它需要哪些参数?
  • 如何生成包含我要调用的函数的lib 对象?
  • 这是使用 CFFI 调用 C 函数的最简单方法吗?我并不特别需要或不希望生成共享库或可再发行包;如果它必须发生,那很好,但没有必要。我还可以尝试哪些其他方法?

我目前的设置是:

  • 操作系统:Windows 10
  • Python:CPython 3.5.1 32 位
  • 点数:8.1.2
  • CFFI:1.6.0
  • C 编译器:Visual C++ Build Tools 2015 附带的任何内容,链接自 this MSDN post

我正在使用来自Christoph Gohlke's repository 的 CFFI 和 pycparser。

【问题讨论】:

    标签: python c unit-testing python-cffi


    【解决方案1】:

    对于我的一个项目,我使用cffi 来测试我的 C 代码。恕我直言cffi 是为 C 代码生成 python 绑定的好工具,因此认为它是用于从 python 调用和测试 C 函数的合理工具。但是,您的代码只能像 C 代码一样跨平台,因为您必须为每个平台编译绑定。

    您可以在下面找到一些可以回答您问题的文档参考。此外,我编写了一些示例代码来说明您将如何使用cffi。举个更大的例子,你可以在https://github.com/ntruessel/qcgc/tree/master/test找到我的项目。

    四个你的例子,build_magic_tests.py 看起来像这样:

    from cffi import FFI
    
    ffibuilder = FFI()
    
    # For every function that you want to have a python binding,
    # specify its declaration here
    ffibuilder.cdef("""
        int add(int a, int b);
                    """)
    
    # Here go the sources, most likely only includes and additional functions if necessary
    ffibuilder.set_source("magic_tests",
        """
        #include "magic.h"
        """, sources=["magic.c"])
    
    if __name__ == "__main__":
        ffibuilder.compile()
    

    要生成magic_tests 模块,您必须运行python build_magic_tests.py。生成的模块可以这样导入和使用:

    from magic_tests import ffi, lib
    
    def run_add():
        assert 4 == lib.add(4, 5)
    

    【讨论】:

    • 很好的答案!我唯一的进一步问题是:头文件中的内容与ffibuilder.cdef() 调用中的内容之间似乎存在一些重复;也就是说,我将两次声明该函数,并且存在可能不同步或引入错误的风险。你认为有办法减少这种重复吗?
    • 很遗憾,我认为目前这是不可能的,至少我没有找到办法做到这一点。恕我直言,这是cffi 的主要缺点之一。可以尝试使用另一个脚本自动生成整个build_magic_tests.py。但是,此脚本必须绕过 ffibuilder.cdef() 方法的限制。
    • 嗯,这很烦人,但不会破坏交易。访问 Python 的单元测试生态系统仍然是值得的。 (而且它仍然比任何 C 单元测试框架都少样板。)
    • 我又想到了一个问题:你会怎么做清理工作?在编译后导入模块的那一刻,如果您想确保每次测试都保持干净,则必须取消导入它。 ffi.compile() 返回 .pyd 文件的名称,该文件可以被删除,但它还会为编译生成一大堆其他内容,这些内容取决于编译器。你知道是否有办法直接从 CFFI 调用中获取 magic_tests.lib 对象?
    • 啊,看来我在谈论 CFFI 所谓的“API,内联”,即。 verify() 函数。它已被弃用,所以我只需要破解它。
    猜你喜欢
    • 2017-04-19
    • 2015-08-28
    • 2013-09-17
    • 2021-02-04
    • 2020-09-13
    • 2017-07-04
    • 1970-01-01
    • 2020-08-15
    • 2018-01-27
    相关资源
    最近更新 更多