Как обернуть функцию C переменными аргументами, используя SWIG - PullRequest
0 голосов
/ 28 мая 2018

Я пытаюсь обернуть функцию C переменными аргументами, используя SWIG, который выглядит следующим образом:

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);
}

Но когда я запускаю функцию в Lua, она работает только тогда, когда я использую 1 аргумент.Я не мог написать в следующем стиле.

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

И я получаю следующую ошибку.

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

Можно ли обернуть функцию C переменными аргументами, используя SWIG?

Буду признателен за любую помощь.Спасибо!

1 Ответ

0 голосов
/ 04 июня 2018

Отказ от ответственности: Не совсем ответ, потому что я не нашел способа переопределить проверку аргументов SWIG, чтобы я мог обрабатывать varargs самостоятельно.Это может быть решено путем объединения методов, которые я показываю ниже, с этим ответом .

Ссылки и дальнейшее чтение

Оболочка простого C

Я подготовил пример преобразования вызова функции Lua с переменным числом в функцию C с переменным числом, используя libffi ( документация ).

В настоящее время код обрабатывает только аргументы int (требуется Lua 5.3), double и const char *.Это может быть тривиально распространено и на другие типы.Имейте в виду, что этот подход крайне небезопасен .Использование неподдерживаемого формата приведет к ошибке сегментации (строка формата не проверяется).Например, компиляция с использованием Lua 5.2 и попытка использовать целочисленный формат, например, так:

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

приведет к

Hello World! 0 0 0

, если вам повезет, и это не даст вам ошибку, нозапуск программы в отладчике памяти, таком как valgrind, покажет, что вы делаете неприятные вещи.

// 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);
}

Компилируя против Lua 5.3, мы можем запустить следующий пример:

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")

Вывод:

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

Попытка в SWIG

Я предложил вариант, который можно использовать из SWIG, но при этом предполагается, что все аргументы могут быть преобразованы в string.Здесь я просто объявляю printf функцией, принимающей десять аргументов типа string (если вам нужно больше, просто увеличьте число).

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

Это вызовет функцию printf с 10строки, которые по умолчанию пусты (NULL).Поэтому я написал action, который преобразует каждый аргумент в соответствующий ему тип (int, double, string).Поскольку средство проверки аргументов SWIG вызывает lua_tostring для каждого аргумента уже, вызов lua_type будет всегда приводить к LUA_TSTRING, независимо от того, какой тип фактического аргумента был.Вот почему я использую lua_tointegerx и lua_tonumberx, чтобы преобразовать строку обратно в исходный тип.В сочетании с крайне неэффективным анализом, основанным на последующем преобразовании, это дает нам оболочку, похожую на простую оболочку C., представленную выше.

%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!

Заключительные замечания

В заключение, это ужасно неэффективный способ форматирования строк в Lua.Я не знаю ни одной переменной функции в C, кроме семейства функций printf, т.е. все они выполняют форматирование строк.Это делается намного эффективнее в Lua с использованием string.format, и следует просто избегать вызова функции, например

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

, в пользу немного более многословного, но гораздо более надежного

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