5

I'm trying to wrap a C function with variable arguments using SWIG which looks like the following.

void post(const char *fmt, ...)
{
    char buf[MAXPDSTRING];
    va_list ap;
    t_int arg[8];
    int i;
    va_start(ap, fmt);
    vsnprintf(buf, MAXPDSTRING-1, fmt, ap);
    va_end(ap);
    strcat(buf, "\n");

    dopost(buf);
}

But When I run the function in Lua, it only works when I use 1 argument. I couldn't write in the following style.

pd.post("NUM : %d", 123);

And I get the following error.

Error in post expected 1..1 args, got 2

Is it possible to wrap a C function with variable arguments using SWIG?

I would appreciate any help. Thanks!

Zack Lee
  • 2,784
  • 6
  • 35
  • 77
  • 1
    http://www.swig.org/Doc1.3/Varargs.html – Henri Menke May 28 '18 at 21:20
  • @HenriMenke Thanks, I read the link but I still don't understand how to wrap the function and whether it is possible in Lua using SWIG. And the link only has Python examples. I would appreciate more detailed answer. – Zack Lee May 29 '18 at 03:25
  • 2
    As you have seen from this page there is no sensible way to wrap C vararg functions unless you are willing to write an awful lot of boilerplate and your own wrapper for the function, defeating the purpose of SWIG. Perhaps you should rethink your design. – Henri Menke May 29 '18 at 04:54
  • 2
    I might be able to hack something together and/or write a patch for SWIG that does this properly. Are you able to do either of a) add a version of `post` which operates on `va_list` or b) limit the number of ABIs you need to support (e.g. just linux/x86) at all, which would slightly simplify adding varargs support to the lua backend. – Flexo Jun 02 '18 at 14:27
  • @Flexo a) Yes b) No, I don't want to limit the number of ABIs to support. – Zack Lee Jun 03 '18 at 04:31
  • One more question: is using GCC extensions acceptable? – Flexo Jun 03 '18 at 11:39
  • @Flexo Yes, I think so. – Zack Lee Jun 04 '18 at 10:30

1 Answers1

3

Disclaimer: Not really an answer, because I didn't find a way to override SWIG's argument checking, so I could process varargs myself. This can be solved by combining the methods I show below with this answer.

Links and further reading

Plain C wrapper

I prepared an example how to convert a call to a variadic Lua function to a variadic C function using libffi (documentation).

Currently the code only handles int (requires Lua 5.3), double and const char * arguments. It can be trivially extended to more types as well. Keep in mind that this approach is extremely unsafe. Using an unsupported format will result in a segmentation fault (the format string goes unchecked). For example compiling against Lua 5.2 and trying to use the integer format like so

printf("Hello World! %d %d %d\n", 1, 5, 7)

will result in

Hello World! 0 0 0

if you are lucky and it doesn't segfault on you, but running the program in a memory debugger like valgrind will reveal that you are doing nasty things.

// clang -Wall -Wextra -Wpedantic -std=c99 -g -I/usr/include/lua5.3 test.c -llua5.3 -lffi

#include <stdio.h>
#include <stdlib.h>

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

#include <ffi.h>

static int l_printf(lua_State *L) {
    typedef union {
        int integer;
        double number;
        const char *string;
    } variant;

    int argc = lua_gettop(L);
    variant *argv = malloc(argc * sizeof(variant));

    ffi_cif cif;
    ffi_type **types = malloc(argc * sizeof(ffi_type *));
    void **values = malloc(argc * sizeof(void *));

    for (int i = 0; i < argc; ++i) {
        int j = i + 1;
        switch (lua_type(L, j)) {
        case LUA_TNUMBER:
#if LUA_VERSION_NUM >= 503
            if (lua_isinteger(L, j)) {
                types[i] = &ffi_type_sint;
                argv[i].integer = lua_tointeger(L, j);
                values[i] = &argv[i].integer;
            } else
#endif
            {
                types[i] = &ffi_type_double;
                argv[i].number = lua_tonumber(L, j);
                values[i] = &argv[i].number;
            }
            break;
        case LUA_TSTRING:
            types[i] = &ffi_type_pointer;
            argv[i].string = lua_tostring(L, j);
            values[i] = &argv[i].string;
            break;
        default:
            puts("Unhandled argment type");
            abort();
            break;
        }
    }

    // If preparing the FFI call fails we simply push -1 to indicate
    // that printf failed
    int result = -1;
    if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, argc, &ffi_type_sint, types) ==
        FFI_OK) {
        ffi_call(&cif, (void (*)())printf, &result, values);
    }

    free(values);
    free(types);
    free(argv);

    lua_pushinteger(L, result);
    return 1;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <script.lua>\n", argv[0]);
        return 1;
    }

    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    lua_pushcfunction(L, l_printf);
    lua_setglobal(L, "printf");

    if (luaL_dofile(L, argv[1]) != 0) {
        fprintf(stderr, "lua: %s\n", lua_tostring(L, -1));
        lua_close(L);
        return 1;
    }

    lua_close(L);
}

Compiling against Lua 5.3 we can run the following example:

print(printf("Hello World! %d %d %d\n", 1, 5, 7) .. " bytes written")
print(printf("Hello %d %f World! %s\n", 1, 3.14, "ABC") .. " bytes written")

Output:

Hello World! 1 5 7
19 bytes written
Hello 1 3.140000 World! ABC
28 bytes written

An attempt in SWIG

I have come up with a variant which is usable from within SWIG, but makes the assumption that all arguments are convertible to string. Here I simply declare printf to be a function taking ten arguments of type string (if you need more, just increase the number).

%varargs(10, const char * = NULL) printf;
int printf(const char *fmt, ...);

This would call the printf function with 10 strings, which are by default empty (NULL). Therefore I wrote an action which converts each argument to its proper type (int, double, string). Because the SWIG argument checker calls lua_tostring on each argument already, a call to lua_type would just always result in LUA_TSTRING, no matter what the actual argument type was. That is why I'm using lua_tointegerx and lua_tonumberx, to convert from the string back to the original type. Combined with an extremely inefficient fallthrough based on succeeding conversion, this gives us a wrapper similar to the plain C wrapper presented above.

%module printf

%{
#include <ffi.h>
%}

%feature("action") printf {
    typedef union {
        int integer;
        double number;
        const char *string;
    } variant;

    int argc = lua_gettop(L);
    variant *argv = malloc(argc * sizeof(variant));

    ffi_cif cif;
    ffi_type **types = malloc(argc * sizeof(ffi_type *));
    void **values = malloc(argc * sizeof(void *));

    for (int i = 0; i < argc; ++i) {
        int j = i + 1;

        int flag = 0;

        types[i] = &ffi_type_sint;
        argv[i].integer = lua_tointegerx(L, j, &flag);
        values[i] = &argv[i].integer;
        if (flag) { continue; }

        types[i] = &ffi_type_double;
        argv[i].number = lua_tonumberx(L, j, &flag);
        values[i] = &argv[i].number;
        if (flag) { continue; }

        types[i] = &ffi_type_pointer;
        argv[i].string = lua_tostring(L, j);
        values[i] = &argv[i].string;
        if (argv[i].string) { continue; }

        puts("Unhandled argment type");
        abort();
        break;
    }

    // If preparing the FFI call fails we simply push -1 to indicate
    // that printf failed
    result = -1;
    if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, argc, &ffi_type_sint, types) ==
        FFI_OK) {
        ffi_call(&cif, (void (*)())printf, &result, values);
    }

    free(values);
    free(types);
    free(argv);
};

%varargs(10, const char * = NULL) printf;
int printf(const char *fmt, ...);
swig -lua test.i
clang -Wall -Wextra -Wpedantic -std=c99 -I/usr/include/lua5.3 -fPIC -shared test_wrap.c -o printf.so -llua5.3 -lffi
local printf = require"printf"
printf.printf("Hello %d %f %s World!\n", 1, 3.14, "ABC")
Hello 1 3.140000 ABC World!

Closing remarks

As a final note, this is a horribly inefficient way of formatting strings in Lua. I am not aware of any variadic function in C other than the printf family of functions, i.e. all of them perform string formatting. This is done much more efficiently in Lua using string.format and a function call like

do_something("Hello %d %f %s World!\n", 1, 3.14, "ABC")

should simply be avoided in favour of the slightly more verbose, but much more robust

do_something(string.format("Hello %d %f %s World!\n", 1, 3.14, "ABC"))
Henri Menke
  • 10,705
  • 1
  • 24
  • 42
  • This is similar to the idea I had in mind. I think I've got a way to get at the raw varargs though, but not done the FFI bits. – Flexo Jun 04 '18 at 07:47
  • 1
    @Flexo How would you have dealt with SWIG's argument checking? I wanted to use `typemap (...)` as in the Python example in the official docs but SWIG determined `...` to be only a single parameter. – Henri Menke Jun 04 '18 at 08:21
  • @HenriMenke Hey, I didn't even know there was such a thing like `string.format()` in Lua as I'm a beginner. I think that would be enough for what I wanted to do. I just wanted to build something similar to `print()` in Lua since I can't print anything to a console using `print()` in my host program. I need to send string to the program in order to do so. That's why I was trying to wrap `post()` which does it. I just wanted to be able to print multiple variables more easily like how `print()` works. – Zack Lee Jun 04 '18 at 10:50
  • 1
    @HenriMenke I had enough fun writing it that even though I didn't follow through with the rest of the solution here it made enough of a question to post as a self-answer: https://stackoverflow.com/a/50686022/168175 – Flexo Jun 04 '18 at 17:42