Lua与C交互简明教程

lua
Lua是一个由标准C编写的轻量级脚本语言,很容易被C++/C调
用,反过来也可以调用C++/C的函数。作用是可以作为扩展脚本与
配置脚本,优势是速度快,支持动态改变。

 

全文的代码参考:点击这里

准备工作

Lua官网下载最新的Release版本,当然也可以根据具体需要下载特定的版本 。然后执行命令(这里下载的安装包是lua-5.2.3.tar.gz,目前官网已经出了最新的5.3):

$tar zxf lua-5.2.3.tar.gz
$cd lua-5.2.3/
$sudo make install

这里需要注意的在Lua的Makefile中默认的安装路径是/usr/local,如需要修改可自行指定。另外Lua5.3参考手册也是参考必须的

Lua与C交互,准备阶段

我们首先得创建Lua在C中使用的环境,利用

lua_State *luaL_newstate (void);
Creates a new Lua state. It calls lua_newstate with an allocator based on the standard C realloc function and then sets a panic function that prints an error message to the standard error output in case of fatal errors.

Returns the new state, or NULL if there is a memory allocation error.

大体上的意思是说luaL_newstate会调用lua_newstate创造一个Lua_State实例,使用C默认的内存分配释放接口。那么其实lua_newstate就是需要自己定义内存分配释放接口。

实例创建了,接下来需要加载Lua的库函数,利用

void luaL_openlibs (lua_State *L);

此接口是加载所有Lua库函数,如果你想选择加载可以参考luaL_requiref

初始化函数

bool init_lua()
{
    s_lua = luaL_newstate();
    if (!s_lua) {
        printf("luaL_newstate failed!\n");
        exit(-1);
    }
    luaL_openlibs(s_lua);
    return true;
}

接下来是加载lua文件,使用接口

int luaL_dofile (lua_State *L, const char *filename);

bool load_lua_file(const char* lua_file)
{
    if (luaL_dofile(s_lua, lua_file) != 0) {
        printf("LOAD LUA %s %s\n", lua_file, BOOT_FAIL);
        return false;
    }
    printf("LOAD LUA %s %s\n", lua_file, BOOT_OK);
    return true;
}

Lua与C交互,开始阶段

这里以一个整数求和的例子来简单体现Lua与C交互,Lua文件内容

function test_func_add(a, b)
    return a + b
end

那么在C中如何调用Lua实现的这个接口?这里需要介绍一点概念,lua_State会维护一个虚拟堆栈(遵循先进后出的原则),堆栈的序号可以从栈顶和栈底计数,从栈底计数,则栈底是1,向栈顶方向递增(1,2,3……)。从栈顶计数,则栈顶是-1,向栈底方向递减(-1,-2,-3)。一般默认从栈顶计数。

堆栈的默认大小是20,可以用lua_checkstack修改,用lua_gettop则可以获得栈里的元素数目。

这里有一个注意点是,如果你的lua_State是全局变量,那么每次对堆栈有新操作时务必使用lua_settop(lua_State, -1)将偏移重新置到栈顶(前提是确保上一个对栈的操作完成)。

接下来去Lua文件中取得test_func_add方法,利用

void lua_getglobal (lua_State *L, const char *name);

然后利用lua_pushnumber,把我们的参数压栈,最后通过

int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);
参数二:你有几个参数要传入Lua func(本段是传入test_func_add需要的参数,应该是2个)
参数三:Lua func处理完后有几个返回值(本段只有1个)
参数四:对错误的处理
具体详细的用法可以参考这里

取得Lua func的返回值也是通过栈的概念,利用lua_tonumber。多啰嗦一句,根据返回值的不同需要调用不同的接口。后文可以看见调用lua_toboolean。

整个代码

int proc_add_operation(int a, int b)
{
    lua_settop(s_lua, -1);
    lua_getglobal(s_lua, "test_func_add");
    lua_pushnumber(s_lua, a);
    lua_pushnumber(s_lua, b);
    int val = lua_pcall(s_lua, 2, 1, 0);
    if (val) {
        printf("lua_pcall_error %d\n", val);
    }
    return (int)lua_tonumber(s_lua, -1);
}

Lua调用C接口,进行阶段

在复杂的需求业务环境中,Lua的处理接口不可能满足所有的数据需求,有时需要原始C程序的一些数据进行判定处理。这就需要在Lua环境调用C程序的接口来处理一些事情。为了满足这个需求,我们首先要做的是对C程序接口进行注册。

void lua_register (lua_State *L, const char *name, lua_CFunction f);
Sets the C function f as the new value of global name

譬如我们定义一个Lua的检查接口

function test_func_check(a)
    local val = test_check_value(a)
    return val
end

其中test_check_value就是我们需要在C实现的接口,那么注册函数可以写为

lua_register(s_lua, "test_check_value", l_test_check_value);

l_test_check_value就是我们需要在C程序中实现的接口

#define target 300
static int l_test_check_value(lua_State * l)
{
    int num = lua_tointeger(l, -1);
    bool check = (num == target);
    lua_pushboolean(l, check);
    return 1;
}

该接口的返回值(本段程序是返回1)是需要返回值的个数,通过lua_pushboolean我们把需要返回的值压入栈。然后Lua func返回,我们再通过lua_toboolean取得Lua func返回值。

bool proc_check_value(int a)
{
    lua_settop(s_lua, -1);
    lua_getglobal(s_lua, "test_func_check");
    lua_pushnumber(s_lua, a);
    int val = lua_pcall(s_lua, 1, 1, 0);
    if (val) {
        printf("lua_pcall_error %d\n", val);
    }
    return (bool)lua_toboolean(s_lua, -1);
}

Lua与C交互运行时改变,最后阶段

Lua与C交互的另外一个重要特点是,Lua脚本的改变不需要重启C程序即可完成,但是需要重新加载对应的Lua的文件,按一般网络服务器的做法,通过特殊的协议或者广播来触发Lua文件的重新加载。本段为了用例方便,调用处理函数前会重新加载。

Lua接口

function test_func_runtime_changes(num)
    local result = test_runtime_changes(num)
    result = result + 100
    return result
end

C相关

int proc_runtime_changes(int a)
{
    lua_settop(s_lua, -1);
    lua_getglobal(s_lua, "test_func_runtime_changes");
    lua_pushnumber(s_lua, a);
    int val = lua_pcall(s_lua, 1, 1, 0);
    if (val) {
        printf("lua_pcall_error %d\n", val);
    }
    return (int)lua_tonumber(s_lua, -1);
}
#define add 100
static int l_test_runtime_changes(lua_State * l)
{
    int num = lua_tointeger(l, -1);
    num += add;
    lua_pushnumber(l, num);
    return 1;
}

主函数处理片段

    printf("== check runtime changes ==\n");
    while(1) {
        sleep(1);
        load_lua_files();
        int result = proc_runtime_changes(400);
        printf("runtime changes %d \n", result);
    }

这里可以看到我们在Lua func test_func_runtime_changes中加了100,在l_test_runtime_changes加了100,传入是400,最后结果是600。当我们重新标记Lua func test_func_runtime_changes中加200,最后结果会变成700。

最近项目中有关Lua应用大幅修改促使我完成了这边文章,想想WOW用Lua作为插件语言,剑网3部分功能用Lua实现,可以说Lua功能多强,多厉害,也可以说用其他哪个语言也可以达到类似或者更好的实现。这里我只想说合适的地方用合适的语言才是合适的选择。

(全文结束)


转载文章请注明出处:漫漫路 - lanindex.com

1 Comment

 Add your comment

Leave a Comment

Your email address will not be published.