Lua进阶用法之Lua和C的接口设计
一:lua/c的接口编程
首先skynet、openresty 都是深度使用 lua 语言的典范;学习 lua 不仅仅要学习基本用法,还要学会使用 c 与 lua 交互,这样才学会了 lua 作为胶水语言的精髓,下面看一下他们两个的调用过程。
虚拟栈:
1.栈中只能存放 lua 类型的值,如果想用 c 的类型存储在栈中,需要将 c 类型转换为 lua 类型;
2.lua 调用 c 的函数都得到一个新的栈,独立于之前的栈;
3.c 调用 lua,每一个协程都有一个栈;一个 lua 虚拟机中可以有多个协程,但同时只能有一个协程在运行;
4.c 创建虚拟机时,伴随创建了一个主协程,默认创建一个虚拟栈;
5.无论何时 Lua 调用 C , 它都只保证至少有 LUA_MINSTACK 这么多的堆栈空间可以使用。 LUA_MINSTACK 一般被定义为 20 ,因此,只要你不是不断的把数据压栈, 通常你不用关心堆栈大小。
对于Lua调C和C调Lua他俩的情况是不同的,我们在Lua调C的过程中,我们不需要去维护这个创建的栈,因为一旦调用 c 的函数都会得到一个新的栈,并且系统会自动释放栈的空间,所以不需要我们去维护。但是对于C调Lua来说的话,需要我们去维护这个栈的大小,避免过大。
闭包和上值:
对于闭包和上值对于Lua来说还是很重要的,大家可以看看这位博主的讲解,十分清楚。
Lua闭包和Upvalue上值-CSDN博客
二:Lua和C的具体代码实现
我们上面说了我们会C调Lua,然后Lua再调C,因此我们将他们都拆开一个一个查看:
1:C调用Lua
C和Lua的区别:我们一旦修改C的代码,那么我们就要重新进行编译,才能进行使用,而Lua不同,它不需要进行重新编译,只需要重新启动一下即可使用,因此对于一些场景来说,我们一些固定的场合使用C语言来实现,而经常改动的比如游戏代码,我们可以使用Lua来写,我们就不用每次都进行编译浪费时间了,这样极大的提高了写代码的效率。因此我们如果在代码中实现文件的重新启动,那么我们就实现了热更新操作。
#include <stdio.h>
#include <stdlib.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
//因为Lua是动态语言,我们可以实现热更新
//c调用Lua函数
static void
call_func_0(lua_State *L, const char* funcname)
{
lua_getglobal(L, funcname); //从Lua中的全局函数,找到这个名称的函数
lua_pushinteger(L, 1); // 压入一个整数到栈顶
lua_call(L, 1, 0); // 调用函数,参数为1,返回值为0
}
//通过c调用LUa的函数
//c调用Lua的话,我们需要维护这个栈个数,因为c调用Lua是同时只有一个协程在工作,所以只有一个栈
//而Lua调c,就不需要维护,只需要注意别一直压栈就可以了,因为Lua每调用一次,就会重新生成一个新的栈。
int main(int argc, char** argv)
{
lua_State *L = luaL_newstate(); // 初始化lua虚拟机
luaL_openlibs(L); // 加载lua标准库
if (argc > 1) { // 加载lua脚本
lua_pushboolean(L, 1); // 压入一个布尔值到栈顶
lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); // 把栈顶的值设置到注册表中,可以在多个C库中共享数据
if ( LUA_OK != luaL_dofile(L, argv[1]) ) { // 加载lua脚本到虚拟机中
const char* err = lua_tostring(L, -1); // 获取错误信息
fprintf(stderr, "err:\t%s\n", err);
return 1;
}
call_func_0(L, "Init"); //开始调用Lua的代码
call_func_0(L, "Loop");
call_func_0(L, "Release");
lua_close(L);
}
return 0;
}
2:被C调用的Lua代码
package.cpath = "luaclib/?.so"
local so = require "uv.c" --这里准备调用C语言中的库函数
--这里又通过Lua调用C的函数
function Init(args)
print("call [init] function", args)
end
function Loop()
print("call [loop] function")
for k, v in ipairs({1,2,3,4,5}) do
so.echo(v) --这个echo函数就是通过调用C库里的函数,也就是Lua调用C
end
end
function Release()
print("call [release] function")
end
在讲被Lua调用的C库的时候,我们先看一下Lua是怎么查找C库中的函数的。在下面的代码中,类似于C中的#include,用来加载头文件的,
local so = require "tbl.c" --这里准备调用C语言中的库函数
当Lua找到此代码后从C库中开始查找luaopen_tbl_c,因此他是有查找的规则的。在下面有讲到。
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
//我们C写的函数
static int
lecho (lua_State *L) {
const char* str = lua_tostring(L, -1);
fprintf(stdout, "%s\n", str);
return 0;
}
static const luaL_Reg l[] = {// 导出给lua使用数组
{"echo", lecho}, //K:函数名,V:函数指针,存放我们自己的函数名
{NULL, NULL},
};
//相当于创建一个表(或者叫栈),然后将变量或者函数压入栈中,后面的数字为索引
//命名规则:luaopen_模块名,当Lua加载C库时,会先搜索这个函数,然后调用这个函数,返回一个表,然后把这个表赋值给模块名,然后就可以使用了。
int
luaopen_tbl_c(lua_State *L) { // local tbl = require "tbl.c"
// 创建一张新的表,并预分配足够保存下数组 l 内容的空间
// luaL_newlibtable(L, l);
// luaL_setfuncs(L, l, 0); //注册函数到表中,0表示函数表的索引为0
// luaL_newlib 函数可以创建一个新的表,然后把函数表 l 中的函数注册到表中,然后把表返回给Lua
luaL_newlib(L, l); // 等价于上面两行
return 1;
}
3:被Lua调用的C库
我们正常的函数,当使用完,他的内存会被回收,里面的变量的值也会消失,但是闭包不同,他还是保存变量的值。并且在这个函数中共享。
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
// 闭包实现: 函数 + 上值 luaL_setfuncs
// lua_upvalueindex(1)
// lua_upvalueindex(2)
//闭包可以在函数内部进行数据共享,因为上值是大家都可以使用的
static int
lecho (lua_State *L) {
lua_Integer n = lua_tointeger(L, lua_upvalueindex(1)); // 取出上值,然后把栈顶的元素替换掉上值
n++;
const char* str = lua_tostring(L, -1); //-1 表示栈顶的元素
fprintf(stdout, "[n=%lld]---%s\n", n, str); //输出到标准输出
lua_pushinteger(L, n); // 把n压入栈顶
lua_replace(L, lua_upvalueindex(1)); // 把栈顶的元素替换成上值
return 0;
}
static const luaL_Reg l[] = {
{"echo", lecho},
{NULL, NULL},
};
//我们压入这个栈之后,变量是在栈中的,所以我们可以把这个栈中的值保存下来,然后在函数中使用,但是是看不到的,所以我们需要使用上值
//命名规则:luaopen_模块名,当Lua加载C库时,会先搜索这个函数,然后调用这个函数,返回一个表,然后把这个表赋值给模块名,然后就可以使用了。
int
luaopen_uv_c(lua_State *L) { // local tbl = require "tbl.c"
// 1. 创建一张新的表,并预分配足够保存下数组 l 内容的空间
luaL_newlibtable(L, l);// 1 创建一个新的表
lua_pushinteger(L, 0);// 2 压入一个整数到栈顶
luaL_setfuncs(L, l, 1);// 上值 把表和函数表关联起来,把栈顶的函数表和栈顶的表关联起来,然后返回Lua一个表
// luaL_newlib(L, l);
return 1;
}
我们在这里可以看到上值的作用:
package.cpath = "luaclib/?.so"
local so = require "uv.c"
so.echo("hello world1")
so.echo("hello world2")
so.echo("hello world3")
so.echo("hello world4")
so.echo("hello world5")
so.echo("hello world6")
so.echo("hello world7")
so.echo("hello world8")
so.echo("hello world9")
我们在这里明明没有传入任何数字,并且没有看到任何数组,只是在3中我们压入了一个上值,然后根据闭包,我们的数值会一直增长,并且在函数内这个上值是共享的。
[n=1]---hello world1
[n=2]---hello world2
[n=3]---hello world3
[n=4]---hello world4
[n=5]---hello world5
...
本篇主要讲解了Lua和C之间的接口设计,加入了Lua是动态语言的作用。0voice · GitHub