tolua 原理解析
lua 虚拟栈
lua 代码
1 |
|
C++ 代码
1 |
|
④ 这个过程,是为了确认栈底是空的,以便后面的操作是按照顺序入栈的且从 1 号栈位开始
⑥操作栈调回结果
- C++ 告诉 lua 虚拟机,函数以输入栈,函数传入 0 个参数,会返回 4 个函数, 不需要错误信息 。分别 对应上面四个参数。 栈中一个元素:
func
- C++ 请求完毕,lua 虚拟机 开始访问栈,从栈中取出
func
。 栈中无元素 - lua 虚拟机 得到 `func` 信息送给lua 程序。** 栈中无元素 **
- lua 程序在调用的 lua 文件全局表 中查找
func
,并运行返回结果1,2,3,4
。 栈中无元素 - lua 程序得到返回结果
1,2,3,4
将结果再压入栈;1
先入栈底,2
再入栈,以此类推。栈中四个元素:1,2,3,4
,顺序为栈底 -> 栈顶 - 最后,C++ 再去栈中读取数据;这里
lua_tostring(L, 1)
是读取函数,不会改变栈内的结果的,所以当地⑥步执行完,栈中还是四个元素:1,2,3,4
若使用
lua_pop(L, 1)
去操作的话,可以弹出指定的位置的栈内容- C++ 告诉 lua 虚拟机,函数以输入栈,函数传入 0 个参数,会返回 4 个函数, 不需要错误信息 。分别 对应上面四个参数。 栈中一个元素:
lua 热更新原理
使用 assetbundle 进行资源的更新,而由于 lua 运行时才编译的特性,所以 lua 文件也可以被看成是一种资源文件(与 fbx、Image 等一样)可以打进 ab 包中
C# 调用 lua
使用 Tolua 的相关类和方法都需要调用命名空间 LuaInterface
调用 lua 脚本必须先创建一个 lua 虚拟机
1
LuaState lua = new LuaState();
在 C# 中运行一段 lua 脚本最简单的方法就是 lua.DoString
1
public object[] DoString(string chunk, string chunkName = "LuaState.DoString")
使用完 lua 虚拟机之后记要销毁
- 先进行 lua 虚拟机的判空
lua.CheckTop
- 先进行 lua 虚拟机的判空
lua 调用 C# 代码
反射
C# 中的反射使用Assembly
定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例
反射用到的命名空间
1 |
|
反射用到的主要类
System.Type
类-通过这个类可以访问任何给定数据类型的信息System.Reflection.Assembly
类-它可以用于访问给定程序集的信息,或者把这个程序集加载到程序中
去反射
把所有的 c# 类的 public
成员变量、成员函数,都导出到一个相对应的 Wrap
类中,而这些成员函数通过特殊的标记,映射到 lua 的虚拟机中,当在 lua 中调用相对应的函数时候,直接调用映射进去的 wrap
函数,然后再调用到实际的 c# 类,完成调用过程
Wrap
在 tolua 生成的 Wrap 文件中,经常遇见如此写法
1 |
|
BeginStaticLibs
首先 BeginStaticLibs
最终会走到这条语句上来:LuaDLL.tolua_beginstaticclass(L, name);
这语句会执行 tolua.dll 的 C 语言对应方法,tolua_beginstaticclass
,该方法最终会在 lua_State
栈顶生成一个名为 name 的table
1 |
|
最终会在 lua_State 栈顶会被压入一个名为 XXX 的 Table
EndStaticLibs
接着先把尾给收掉,EndStaticLibs
最后会来到 tolua.c
这边的 tolua_endstaticclass
方法,该方法最终会将栈顶元素弹出并将其设置为 - 2 位置的元表
1 |
|
弹出栈顶元素,将其设置为 XXX(Empty Table) 的元表
RegFunction
RegFunction
先是将要注册的方法转换成了供平台使用的指针,传递到 C 中生成可以供 lua 使用的 LuaCSFunction
函数
1 |
|
tolua.c 文件中 tolua_function
对传入进的函数进行了绑定
1 |
|
lua_rawset(L, -3);
赋值操作:将栈顶作为 值——
CClosure
,倒数第二位作为 键——name
,-3 位置作为 table 进行赋值 ,即XXX[name] = CClosure
实际上一个 C# 方法的指针其实被封装了两层:
lua_pushcfunction(L, fn)
时 将fn
封装进了CClosure
中lua_pushcclosure(L, tolua_closure, 2)
时将封装了fn
的CClosure
再一次封装进了新的CClosure
中
当在 lua 中调用注册的方法时,实际上是在调用最外层的 CClosure
结构体,它其中的方法是 tolua_closure
,而 C# 方法指针fn
作为该结构体栈中的值被存放着upvalue
1 |
|
lua_pushfunction
:实际上压入函数的过程就是形成闭包的过程,在 lua 中函数是以闭包的形式被保存的1
2/* lua.h */
#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) /* 宏 压入闭包方法 n 为 0 */lua_pushclosure
:生成闭包,将函数存放在闭包结构体中,并将栈顶 n 个元素一同压入闭包内的栈1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33typedef struct CClosure {
ClosureHeader;
lua_CFunction f;
TValue upvalue[1]; /* list of upvalues */
} CClosure;
/* lua.h */
LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
/* lapi.c */
LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {
lua_lock(L);
if (n == 0) {
setfvalue(L->top, fn);
api_incr_top(L);
}
else {
CClosure *cl;
api_checknelems(L, n);
api_check(L, n <= MAXUPVAL, "upvalue index too large"); /* #define MAXUPVAL 255 */
cl = luaF_newCclosure(L, n);/* 创建了闭包结构体 */
cl->f = fn;
L->top -= n; /* 将栈顶 n 个元素移除并压入闭包的栈中 upvalue */
while (n--) {
setobj2n(L, &cl->upvalue[n], L->top + n);
/* does not need barrier because closure is white */
}
setclCvalue(L, L->top, cl);
api_incr_top(L);
luaC_checkGC(L);
}
lua_unlock(L);
}