Как обернуть функцию C переменными аргументами с помощью SWIG

Я пытаюсь обернуть функцию 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?

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

swig.org/Doc1.3/Varargs.html
Henri Menke 28.05.2018 23:20

@HenriMenke Спасибо, я прочитал ссылку, но до сих пор не понимаю, как обернуть функцию и возможно ли это в Lua с использованием SWIG. И в ссылке есть только примеры Python. Буду признателен за более подробный ответ.

Zack Lee 29.05.2018 05:25

Как вы видели на этой странице, нет разумного способа обернуть функции C vararg, если вы не готовы написать очень много шаблонов и свою собственную оболочку для функции, что противоречит цели SWIG. Возможно, вам стоит переосмыслить свой дизайн.

Henri Menke 29.05.2018 06:54

Я мог бы что-нибудь вместе взломать и / или написать патч для SWIG, который сделает это правильно. Можете ли вы выполнить одно из следующих действий: а) добавить версию post, которая работает на va_list, или б) вообще ограничить количество поддерживаемых ABI (например, только linux / x86), что немного упростит добавление поддержки varargs в lua бэкэнд.

Flexo 02.06.2018 16:27

@Flexo a) Да б) Нет, я не хочу ограничивать количество поддерживаемых ABI.

Zack Lee 03.06.2018 06:31

Еще один вопрос: допустимо ли использование расширений GCC?

Flexo 03.06.2018 13:39

@Flexo Да, я так думаю.

Zack Lee 04.06.2018 12:30
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
7
668
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Заявление об ограничении ответственности: Не совсем ответ, потому что я не нашел способа переопределить проверку аргументов 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"))

Это похоже на идею, которую я имел в виду. Я думаю, что у меня есть способ добраться до сырых varargs, но не сделал бит FFI.

Flexo 04.06.2018 09:47

@Flexo Как бы вы поступили с проверкой аргументов SWIG? Я хотел использовать typemap (...), как в примере Python в официальной документации, но SWIG определил, что ... - это только один параметр.

Henri Menke 04.06.2018 10:21

@HenriMenke Привет, я даже не знал, что в Lua есть такая вещь, как string.format(), так как я новичок. Думаю, этого было бы достаточно для того, чем я хотел заниматься. Я просто хотел создать что-то похожее на print() в Lua, так как я не могу ничего напечатать на консоль, используя print() в моей основной программе. Для этого мне нужно отправить строку в программу. Вот почему я пытался обернуть post(), который это делает. Я просто хотел иметь возможность более легко печатать несколько переменных, например, как работает print().

Zack Lee 04.06.2018 12:50

@HenriMenke Мне было достаточно весело писать это, что, хотя я не выполнил остальную часть решения здесь, он сделал достаточно вопроса, чтобы опубликовать его как самостоятельный ответ: stackoverflow.com/a/50686022/168175

Flexo 04.06.2018 19:42

Другие вопросы по теме