让 SWIG 生成的 Lua 包装器允许可变参数函数的额外参数

当我使用 SWIG 生成可变参数函数的包装器时,它会插入检查传递给函数的精确参数数量的代码,例如:

%inline %{
void foobar(const char *fmt, ...) {}

生成的包装器总是插入以下内容:

SWIG_check_num_args("foobar",1,1)

如何解决这个问题并允许大于或等于 1 的任意数量的参数?

点赞
用户168175
用户168175

可以通过一系列的hack方法绕过这个问题,并生成当传递额外参数时不会产生错误的代码。

本质上,这些hack方法可以归结为一种观察结果——在 SWIG_check_num_args 调用之前,我们只能控制本地变量的声明和初始化。然而,我们可以利用这一点来制作一个本地变量,阴影一个全局变量,并根据此更改行为。

module test

%runtime%{
static inline int SWIG_check_num_args_real(lua_State* L, const char *fn, int min_args, int max_args) {
    SWIG_check_num_args(fn, min_args, max_args);
    return 1;
fail:
    return 0;
}
#undef SWIG_check_num_args
#define SWIG_check_num_args(name,a,b) if (!SWIG_check_num_args_real(L,name,a,_global_is_varargs?1024:b)) goto fail;
static const int _global_is_varargs=0;
%}

%typemap(in)(const char *fmt,...)(const int _global_is_varargs = 1)%{
    $typemap(in,const char *)//默认的字符串东西
    //TODO:在这里处理其余的参数
%}

%inline%{
void foobar(const char *fmt,...) {}
void boring(int i) {}
%}

为了让这个概念可行,我们需要插入一些代码,用我们可以控制的内容替换默认的宏 SWIG_check_num_args,最好不要完全重新编写宏,以防它稍后发生变化。为此,我们设置一个内联函数,以便我们可以有另一个 fail 标签供宏使用,但可以在以后的 #undef 前应用原始定义的宏。

有了这个内联函数,我们就可以将宏重新定义为一个稍微更可取的宏,它使用一个名为 _global_is_varargs 的变量来动态修改宏的第三个参数,并在我们处于变量参数的情况下将其增加。 (1024是完全任意的,INT_MAX或一些特定实现的限制同样适用)。

我们计划遮盖的变量需要以魔术前缀 _global_ 开始,以避免自动附加后缀到局部变量名称 (http://www.swig.org/Doc3.0/Typemaps.html#Typemaps_nn23)。由于一切都是const,聪明的编译器可能会在不增加额外运行时开销的情况下执行此操作。

然后,我们可以编写typemaps来实际创建一个本地变量来遮蔽全局变量,并对Lua参数执行一些实际工作。在我的示例中,我将其作为多参数typemap,但可能不是必需的。(我喜欢确切的匹配方式)。

有了这个,我们可以像预期的那样调用函数(虽然它没有做任何有用的事情,这是预处理器/ FFI/ ABI/ gcc扩展有趣的练习):

Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> m=require('test')
> m.boring()
stdin:1: Error in boring expected 1..1 args, got 0
stack traceback:
        [C]: in function 'test.boring'
        stdin:1: in main chunk
        [C]: in ?
> m.boring(1)
> m.foobar()
stdin:1: Error in foobar expected 1..1024 args, got 0
stack traceback:
        [C]: in function 'test.foobar'
        stdin:1: in main chunk
        [C]: in ?
> m.foobar('')
> m.foobar('',1)
> m.foobar('',1,2,3)
2018-06-04 17:40:34