快速掌握Lua 5.3 —— 扩展你的程序

Posted VermillionTear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速掌握Lua 5.3 —— 扩展你的程序相关的知识,希望对你有一定的参考价值。

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()中不能将返回值出栈。这个工作需要由调用者将返回值转存后,再手动将结果出栈。

以上是关于快速掌握Lua 5.3 —— 扩展你的程序的主要内容,如果未能解决你的问题,请参考以下文章

高速掌握Lua 5.3 —— 扩展你的程序

快速掌握Lua 5.3 —— userdata

快速掌握Lua 5.3 —— 资源管理

快速掌握Lua 5.3 —— 从Lua中调用C函数

快速掌握Lua 5.3 —— 资源管理

快速掌握Lua 5.3 —— 调试库