重命名/重新声明 CFFI 的函数?

Renaming/re-declaring functions for CFFI?

提问人:JamesTheAwesomeDude 提问时间:11/8/2023 更新时间:11/8/2023 访问量:21

问:

我有一个 C 库,我正在尝试为其创建(外行,API 模式)CFFI 绑定。C 库提供了每个函数的各种实现,但它们都添加了这个巨大的、令人讨厌的前缀。

例如,GreenSpam 子模块中 foo 函数的 AVX 优化实现被命名为 ;我将把它公开为 ,并在 -time 透明地选择 CPU 优化。THELIBRARY_GREENSPAM_OPTIMIZED_AVX_foo()TheLibrary.Spam.GreenSpam.fooimport

我能够在 -time 确定每个模块的这个前缀,所以我想尝试尽可能地倾向于 DRY。我的代码目前看起来像这样,正如你所看到的,它与 DRY 一样可怕,并且没有提供任何简单或明显的途径来推进运行时算法的选择:ffibuilder.compile()

# TheLibrary/Spam/GreenSpam.py
from . import _greenspam_generic
from . import _greenspam_optimized_avx  # TODO

__all__ = ['foo', 'oof']

def foo(bar):
    """GreenSpam foo"""
    _out = _greenspam_generic.ffi.new(f"uint8_t[{len(bar):d}]")
    _greenspam_generic.lib.THELIBRARY_GREENSPAM_GENERIC_foo(bar, len(bar), _out)
    # void function never errors
    return bytes(_out)

def oof(baz):
    """GreenSpam oof"""
    _out = _greenspam_generic.ffi.new(f"uint8_t[{len(baz):d}]")
    err = _greenspam_generic.lib.THELIBRARY_GREENSPAM_GENERIC_oof(baz, len(baz), _out)
    if err:
        raise RuntimeError("oof")
    return bytes(_out)
# TheLibrary/Spam/PurpleSpam.py
from . import _purplespam_generic
from . import _purplespam_optimized_avx  # TODO

__all__ = ['foo', 'oof']

def foo(bar):
    """PurpleSpam foo"""
    _out = _purplespam_generic.ffi.new(f"uint8_t[{len(bar):d}]")
    _purplespam_generic.lib.THELIBRARY_PURPLESPAM_GENERIC_foo(bar, len(bar), _out)
    # void function never errors
    return bytes(_out)

def oof(baz):
    """PurpleSpam oof"""
    _out = _purplespam_generic.ffi.new(f"uint8_t[{len(baz):d}]")
    err = _purplespam_generic.lib.THELIBRARY_PURPLESPAM_GENERIC_oof(baz, len(baz), _out)
    if err:
        raise RuntimeError("oof")
    return bytes(_out)

我真的很想在运行时将这些 C 库函数名称扁平化,理想情况下公开别名,以使我的绑定代码更干净、更好,特别是使我能够添加这些导入时 CPU 选择,而不必再次乘法复制我的代码。ffi.compile()(fr'\b{re.escape(prefix)}_(\w+)\b', r'\1')

我想尽可能密切地跟踪上游库,因此在 C 源代码上进行转换以删除这些前缀会很痛苦,我特别想询问这样做的替代方案

但是,例如,我可以在 ,规范化别名添加到损坏的名称中,特别是如果可以以 DRY 为中心的方式完成。(我确实尝试过函数引用,但显然这些只是C++?ffibuilder.set_source()


我目前正在查看的特定库是 PQClean,但我希望我在将来的绑定中也会遇到这种情况,所以这是反对过多地深入转换 C 源代码的另一点,因为每次我遇到一个新库时,我都必须重新重复这项工作,并且每次该库重构其标头意大利面条时都可能再次重复。

命名空间 函数声明 python-cffi

评论

0赞 JamesTheAwesomeDude 11/8/2023
这*是*#77275165“在 Python CFFI 中转换 C 函数名称”的副本,在我提出同样问题前一个小时,它被奇怪地问到,但在发布后立即被作者删除,没有公开给出任何理由......(*诚然,我不确定 SE 是否允许您在删除问题时附上原因,但我的困惑仍然存在)

答:

1赞 Armin Rigo 11/8/2023 #1

你当然可以玩一些 Python 级别的技巧。我会做这样的事情......(诚然,这使用了相当多的各种 Python hack)

# to make the builds "mylib_<suffix>.so"
for suffix in ['generic', 'optimized_avx']:
    builder = FFIBuilder()
    builder.set_source("mylib_" + suffix,
    f"""
        #include <mylib.h>
        int foo_{suffix}(int x, int y);
        void bar_{suffix}(void);
    """)
    builder.compile()

# minimal source code for mylib_generic.py
import _mylib_maker
globals().update(_mylib_maker.make("generic"))

# minimal source code for mylib_optimized_avx.py
import _mylib_maker
globals().update(_mylib_maker.make("optimized_avx"))

# actual Python-side wrappers defined in _mylib_maker.py
def make(suffix):
    mod = __import__("mylib_" + suffix)
    c_foo = getattr(mod, "foo_" + suffix)
    c_bar = getattr(mod, "bar_" + suffix)

    my_funcs = []

    @my_funcs.append
    def foo(x, y):
        result = c_foo(x, y)
        if result < 0: raise Exception
        return result

    @my_funcs.append
    def bar():
        return c_bar()

    return {func.__name__: func for func in my_funcs}