Lua通讯原理
Lua是用C编写的,C#这边操作的最底层,实际上是调用了DLL封装好的Lua API
Lua中最关键的概念就是Lua栈,所有交互都通过这个栈进行,这个栈的核心概念只有两点
- 1是栈底,往上递增
- -1是栈顶,往下递减
C调用Lua
取值
/*1.创建一个state*/
lua_State *L = luaL_newstate();
/*2.入栈操作*/
lua_pushstring(L, "I am Bob~");
lua_pushnumber(L,2018);
/*3.取值操作*/
/*判断是否可以转为string*/
if( lua_isstring(L,1)){
/*转为string并返回*/
printf("%s",lua_tostring(L,1));
}
if( lua_isnumber(L,2)){
printf("%g ",lua_tonumber(L,2));
}
函数
假设有如下Lua代码
--game.lua
name = "Bob"
age = 18
player = { name = "bob", sex = "boy"}
function getCoin (curCoin,change)
return curCoin+change
end
//读取参数,压入栈
lua_getglobal(L,"name");
printf("name = %s",lua_tostring(L,-1));
//读取参数,压入栈
lua_getglobal(L,"age");
printf("age = %g ",lua_tonumber(L,-1));
//读取表
lua_getglobal(L, "player");
//取表中元素
lua_getfield(L, -1 ,"name");
printf("player name = %s",lua_tostring(L,-1));
lua_getfield(L,-2,"sex");
printf("player sex = %s",lua_tostring(L,-1));
//取函数
lua_getglobal(L,"getCoin");
lua_pushnumber(L,5);
lua_pushnumber(L,3);
lua_pcall(L,2,1,0);//2-参数格式,1-返回值个数,调用函数,函数执行完,会将返回值压入栈中
printf("5 + 3 = %g",lua_tonumber(L,-1));
其实关键的是最后几行,我们先压入一个函数,再压入两个参数,最后调用,调用结果也会被压入栈,所以我们可以通过-1获取(栈顶)
注意,执行完之后,Lua栈会把相关函数和参数都弹出,什么意思?看一下执行完之后的栈情况你就懂了
到index5应该是很好理解的, 只是按照顺序压入了一些数据而已,而再调用函数前,index6应该是压入的函数,index7和index8则是压入的两个参数
调用函数后index678都被弹出了,然后压入一个返回值
lua_getglobal(L,”name”);
- 先把name压入栈
- 通过栈顶元素(也就是name)去Lua寻找对应的值,弹出栈顶元素,并压入对应的值(此时栈顶就是Bob了)
lua_getfield(L, -1 ,”name”);
这个想对来说会烧脑一点
-
lua_pushstring(L,name),会先把name字符串压入栈,此时index-1=name,因为上面一行我们提前压入过一个table,所以现在index-2是table(Player)
-
lua_gettable(L,-2),这个API的意思是,把栈顶元素当做key,去指定元素(-2)里取值,现在-2是Player,所以此时就等于是Player[name](栈顶元素是name,-2对应的元素是Player)
然后回弹出栈顶元素,并压入返回值,所以index-1变成了bob
Lua调用C
这个相对简单很多,只要把符合规范的C函数注册到Lua里,然后就可以和普通方法一样调用了
规范:typedef int (**lua_CFunction) (lua_State **L);
然后就很简单了
static int getMoney(lua_State *L)
{
// 向函数栈中压入2个值
lua_pushnumber(L, 915);
lua_pushstring(L,"Bob");
//这个函数的返回值则表示函数返回时有多少返回值被压入Lua栈。
//Lua的函数是可以返回多个值的
return 2;
}
//注册函数
lua_register(L,"getMoney",getMoney);
然后再Lua内部,其实是分两步进行的
lua_pushcfunction(L, getMoney); //将函数放入栈中
lua_setglobal(L, "getMoney"); //设置lua全局变量getMoney
先把函数压入栈,然后setglobal,弹出栈顶元素
C#调用C/Lua
C#通过P/Invoke间接调用C的API,间间接调用Lua
对应string,int等等简单值类型没啥好说的,有对应的push_string等方法,但是C#的类对象Lua可识别不了,所以对于Class,C#传递的肯定是一个指针
public void Push(RealStatePtr L, object o)
{
bool is_first;
int type_id = getTypeId(L, type, out is_first);
...
// C#侧进行缓存
index = addObject(o, is_valuetype, is_enum);
// 将代表对象的索引push到lua
LuaAPI.xlua_pushcsobj(L, index, type_id, needcache, cacheRef);
}
首先把对象在C#侧缓存起来
int addObject(object obj, bool is_valuetype, bool is_enum)
{
int index = objects.Add(obj);
//如果是枚举
if (is_enum)
{
enumMap[obj] = index;
}
//如果不是枚举也不是值类型
else if (!is_valuetype)
{
reverseMap[obj] = index;
}
return index;
}
然后会把index作为key传递给Lua,传递的是index哦,代表对象的索引号
type_id作为meta_ref,代表的是对象类型的索引号
// xlua.c
LUA_API void xlua_pushcsobj(lua_State *L, int key, int meta_ref, int need_cache, int cache_ref) {
...
}
到Lua侧后,会创建一张UserData,并把UserData和key,meta_ref关联起来
到这里,我们在Lua侧可以通过索引拿到C#对象,但是我们不知道对象里的属性和方法啊!
所以还得给这个类对象注册他的元表,通过meta_ref!
元表在C#侧,是通过框架(xLua/toLua)生成的一大堆warp文件,都是静态的,会在Lua虚拟机启动的时候注册到Lua虚拟机内
那么怎么找到这些元表呢?没错,就是通过type_id,每个类型注册元表的时候都会对应一个type_id