如何使用 SWIG 包装带有可变参数的 C 函数
How to wrap a C function with variable arguments using SWIG
我正在尝试使用如下所示的 SWIG 包装带有可变参数的 C 函数。
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
是否可以使用 SWIG 包装带有可变参数的 C 函数?
如有任何帮助,我将不胜感激。谢谢!
免责声明: 不是真正的答案,因为我没有找到覆盖 SWIG 参数检查的方法,所以我可以自己处理可变参数。这可以通过将我在下面显示的方法与 .
相结合来解决
链接和进一步阅读
- Variable Length Arguments (a.k.a, "The horror. The horror.") 在 SWIG 文档中
- Create va_list dynamically 在 Stack Overflow
- How do I fill a va_list 在 Stack Overflow
普通 C 包装器
我准备了一个示例,说明如何使用 libffi (documentation).
将对可变参数 Lua 函数的调用转换为可变参数 C 函数
目前代码仅处理 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, ...);
这将使用 10 个字符串调用 printf
函数,这些字符串默认为空 (NULL
)。因此,我写了一个 action
将每个参数转换为其正确的类型(int
、double
、string
)。因为 SWIG 参数检查器已经对每个参数调用 lua_tostring
,所以无论实际参数类型是什么,对 lua_type
的调用总是会导致 LUA_TSTRING
。这就是我使用 lua_tointegerx
和 lua_tonumberx
将字符串转换回原始类型的原因。结合基于成功转换的极其低效的 fallthrough,这为我们提供了一个类似于上面介绍的普通 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 中,这是一种非常低效的格式化字符串的方式。除了 printf
系列函数,我不知道 C 中有任何可变参数函数,即它们都执行字符串格式化。这在 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"))
我正在尝试使用如下所示的 SWIG 包装带有可变参数的 C 函数。
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
是否可以使用 SWIG 包装带有可变参数的 C 函数?
如有任何帮助,我将不胜感激。谢谢!
免责声明: 不是真正的答案,因为我没有找到覆盖 SWIG 参数检查的方法,所以我可以自己处理可变参数。这可以通过将我在下面显示的方法与
链接和进一步阅读
- Variable Length Arguments (a.k.a, "The horror. The horror.") 在 SWIG 文档中
- Create va_list dynamically 在 Stack Overflow
- How do I fill a va_list 在 Stack Overflow
普通 C 包装器
我准备了一个示例,说明如何使用 libffi (documentation).
将对可变参数 Lua 函数的调用转换为可变参数 C 函数目前代码仅处理 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, ...);
这将使用 10 个字符串调用 printf
函数,这些字符串默认为空 (NULL
)。因此,我写了一个 action
将每个参数转换为其正确的类型(int
、double
、string
)。因为 SWIG 参数检查器已经对每个参数调用 lua_tostring
,所以无论实际参数类型是什么,对 lua_type
的调用总是会导致 LUA_TSTRING
。这就是我使用 lua_tointegerx
和 lua_tonumberx
将字符串转换回原始类型的原因。结合基于成功转换的极其低效的 fallthrough,这为我们提供了一个类似于上面介绍的普通 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 中,这是一种非常低效的格式化字符串的方式。除了 printf
系列函数,我不知道 C 中有任何可变参数函数,即它们都执行字符串格式化。这在 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"))