Q:如何在C中调用Lua的函数?
A:
1、将函数名入栈。
2、将参数按照形参的顺序依次入栈。
3、调用函数。此过程会将函数的参数出栈,调用完成后将函数的返回值入栈。
4、获取函数的返回值。
“config.lua”文件中:
function f(x, y)
return (x ^ 2 * math.sin(y)) / (1 - x)
end
“main.c”文件中:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
void error(lua_State *L, const char *fmt, ...)
{
va_list argp;
va_start(argp, fmt);
vfprintf(stderr, fmt, argp);
va_end(argp);
lua_close(L);
exit(EXIT_FAILURE);
}
void load(lua_State *L, char *filename)
{
if(luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))
error(L, "cannot run configuration file: %s", lua_tostring(L, -1));
}
// 函数内部调用Lua的同名函数。
double f(lua_State *L, double x, double y)
{
double z = 0.0;
lua_getglobal(L, "f"); // 将函数名入栈。
// 将参数按照Lua中函数的形参顺序依次入栈。
lua_pushnumber(L, x);
lua_pushnumber(L, y);
if(lua_pcall(L, 2, 1, 0) != 0) // 以保护模式调用函数,2个参数,1个返回值。
error(L, "error running function `f‘: %s", lua_tostring(L, -1));
if(!lua_isnumber(L, -1)) // 获取函数的返回值。
error(L, "function `f‘ must return a number");
z = lua_tonumber(L, -1);
lua_pop(L, 1); // 弹出返回值,保证栈的状态与调用该函数时一样。
return z;
}
int main(void)
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
load(L, "config.lua");
printf("%f\n", f(L, 0.5, 0.5));
lua_close(L);
return 0;
}
prompt> gcc main.c -llua -ldl -lm -Wall
prompt> ./a.out
0.239713
Q:如何通过C中调用Lua函数的形式实现类似于C++中的函数重载?
A:我们继续扩展上面例子中的程序(本例也有些类似于JNI的调用方式),
“main.c”文件中:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
void error(lua_State *L, const char *fmt, ...)
{
va_list argp;
va_start(argp, fmt);
vfprintf(stderr, fmt, argp);
va_end(argp);
lua_close(L); // 关闭Lua状态机。
exit(EXIT_FAILURE); // 以失败退出。
}
void load(lua_State *L, char *filename)
{
if(luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))
error(L, "cannot run configuration file: %s", lua_tostring(L, -1));
}
/* "func": 被调用的Lua函数的名字。
* "sig": 参数以及返回值得类型。
‘d‘: double。
‘i‘: integer。
‘s‘: string。
‘>‘: 参数与返回值之间的分隔符。如果函数没有返回值,可以忽略。
* "...": 参数以及返回值变量。
*/
void call_va(lua_State *L, const char *func, const char *sig, ...)
{
va_list vl;
int narg = 0, nres = 0; // 参数的个数以及返回值的个数。
va_start(vl, sig);
lua_getglobal(L, func); // 将Lua函数入栈。
narg = 0;
while(*sig) // 逐一判断参数的类型,并以合适的方式入栈。
{
switch(*sig++)
{
case ‘d‘: /* double argument */
lua_pushnumber(L, va_arg(vl, double));
break;
case ‘i‘: /* int argument */
lua_pushinteger(L, va_arg(vl, int));
break;
case ‘s‘: /* string argument */
lua_pushstring(L, va_arg(vl, char *));
break;
case ‘>‘: // 参数已经获取完毕,跳出"while"。
goto endwhile;
default:
error(L, "invalid option(%c)", *(sig - 1));
}
narg++;
/* 将栈的大小加1。如果执行失败(没有足够的内存空间了),则报错。
* 最后一个参数是错误信息中显示的额外信息("NULL"代表无额外信息)。
* 由于本函数接收可变参数,即可以接收任意多的参数,所以必须检查栈空间的大小。
*/
luaL_checkstack(L, 1, "too many arguments");
}
endwhile:
nres = strlen(sig); // 获取返回值的数量。
if(lua_pcall(L, narg, nres, 0) != 0)
error(L, "error running function `%s‘: %s", func, lua_tostring(L, -1));
nres = -nres; // 返回值数量取反,可以通过负索引从第一个返回值开始获取。
while(*sig) // 获取返回值。
{
switch(*sig++)
{
case ‘d‘: /* double result */
if(!lua_isnumber(L, nres))
error(L, "wrong result type");
*va_arg(vl, double *) = lua_tonumber(L, nres);
break;
case ‘i‘: /* int result */
if(!lua_isinteger(L, nres))
error(L, "wrong result type");
*va_arg(vl, int *) =(int)lua_tointeger(L, nres);
break;
case ‘s‘: /* string result */
if(!lua_isstring(L, nres))
error(L, "wrong result type");
*va_arg(vl, const char **) = lua_tostring(L, nres);
break;
default:
error(L, "invalid option(%c)", *(sig - 1));
}
nres++;
}
va_end(vl);
}
int main(void)
{
double z = 0.0f;
lua_State *L = luaL_newstate();
luaL_openlibs(L);
load(L, "config.lua");
// 调用Lua的"f"函数,第一个参数是浮点数,第二个参数是字符串,函数返回一个浮点数由"z"接收。
call_va(L, "f", "ds>d", 0.5, "0.5", &z);
printf("%f\n", z);
lua_close(L);
return 0;
}
prompt> gcc main.c -llua -ldl -lm -Wall
prompt> ./a.out
0.239713
附加:
1、call_va()
中无需判断指定的”func”是否是Lua中的一个有效的函数,lua_pcall()
可以捕捉这个错误。
2、由于Lua函数的返回值可能是字符串,所以call_va()
中不能将返回值出栈。这个工作需要由调用者将返回值转存后,再手动将结果出栈。
时间: 2024-10-10 16:30:30