Я пытаюсь обернуть функцию 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?
Буду признателен за любую помощь. Спасибо!
@HenriMenke Спасибо, я прочитал ссылку, но до сих пор не понимаю, как обернуть функцию и возможно ли это в Lua с использованием SWIG. И в ссылке есть только примеры Python. Буду признателен за более подробный ответ.
Как вы видели на этой странице, нет разумного способа обернуть функции C vararg, если вы не готовы написать очень много шаблонов и свою собственную оболочку для функции, что противоречит цели SWIG. Возможно, вам стоит переосмыслить свой дизайн.
Я мог бы что-нибудь вместе взломать и / или написать патч для SWIG, который сделает это правильно. Можете ли вы выполнить одно из следующих действий: а) добавить версию post, которая работает на va_list, или б) вообще ограничить количество поддерживаемых ABI (например, только linux / x86), что немного упростит добавление поддержки varargs в lua бэкэнд.
@Flexo a) Да б) Нет, я не хочу ограничивать количество поддерживаемых ABI.
Еще один вопрос: допустимо ли использование расширений GCC?
@Flexo Да, я так думаю.





Заявление об ограничении ответственности: Не совсем ответ, потому что я не нашел способа переопределить проверку аргументов SWIG, чтобы я мог обрабатывать varargs сам. Это можно решить, объединив методы, которые я показываю ниже, с этот ответ.
Я подготовил пример того, как преобразовать вызов вариативной функции 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, но при этом предполагается, что все аргументы можно преобразовать в 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 Как бы вы поступили с проверкой аргументов SWIG? Я хотел использовать typemap (...), как в примере Python в официальной документации, но SWIG определил, что ... - это только один параметр.
@HenriMenke Привет, я даже не знал, что в Lua есть такая вещь, как string.format(), так как я новичок. Думаю, этого было бы достаточно для того, чем я хотел заниматься. Я просто хотел создать что-то похожее на print() в Lua, так как я не могу ничего напечатать на консоль, используя print() в моей основной программе. Для этого мне нужно отправить строку в программу. Вот почему я пытался обернуть post(), который это делает. Я просто хотел иметь возможность более легко печатать несколько переменных, например, как работает print().
@HenriMenke Мне было достаточно весело писать это, что, хотя я не выполнил остальную часть решения здесь, он сделал достаточно вопроса, чтобы опубликовать его как самостоятельный ответ: stackoverflow.com/a/50686022/168175