从零开始的SLua(二)SLua的C#函数导出

继续Slua 的第二个Demo, 这个Demo 演示的主要是将 c# 函数注入到 lua 中并在 lua 中调用,不对的地方还望大佬们指正

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
33
34
35
36
37
38
39
40
41
42
43
// this exported function don't generate stub code if it had MonoPInvokeCallbackAttribute attribute, 
// only register it
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static public int instanceCustom(IntPtr l)
{
Custom self = (Custom)LuaObject.checkSelf(l);
LuaObject.pushValue(l, true);
LuaDLL.lua_pushstring(l, "xiaoming");
LuaDLL.lua_pushstring(l, "hanmeimei");
LuaDLL.lua_pushinteger(l, self.v);
return 4;
}

// this exported function don't generate stub code, only register it
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
[StaticExport]
static public int staticCustom(IntPtr l)
{
LuaObject.pushValue(l, true);
LuaDLL.lua_pushstring(l, vs);
LuaObject.pushObject(l, c);
return 3;
}
public int this[string key]
{
get
{
if (key == "test")
return v;
return 0;
}
set
{
if (key == "test")
{
v = value;
}
}
}
public string getTypeName(Type t)
{
return t.Name;
}

Demo 中展示了四个不一样的函数,前面两个就是标准的注册函数了,注释也给了解释,无需再生成注册用的函数代码,后面两个则没做处理,OK,我们一步一步来:

首先我们在 LuaGenCode.cs 中找到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[MenuItem("SLua/Custom/Make")]
static public void Custom()

// export self-dll
assembly = Assembly.Load("Assembly-CSharp");
types = assembly.GetExportedTypes();
foreach (Type t in types)
{
if (t.IsDefined(typeof(CustomLuaClassAttribute), false) || namespaces.Contains(t.Namespace))
{
fun(t, null);
}
}

CustomExport.OnAddCustomClass(fun);

这里一大堆判断逻辑我们先不去深究,根据注释提示我们可以看到它是遍历了所有带CustomLuaClassAttribute 修饰的类,然后按照模板去为这个类生成新的 cs 代码, 我们直接看新生成的代码:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class Lua_Custom : LuaObject {
[SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
[UnityEngine.Scripting.Preserve]
static public int getTypeName(IntPtr l) {
try {
Custom self=(Custom)checkSelf(l);
System.Type a1;
checkType(l,2,out a1);
var ret=self.getTypeName(a1);
pushValue(l,true);
pushValue(l,ret);
return 2;
}
catch(Exception e) {
return error(l,e);
}
}
[SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
[UnityEngine.Scripting.Preserve]
static public int getInterface(IntPtr l) {
try {
Custom self=(Custom)checkSelf(l);
var ret=self.getInterface();
pushValue(l,true);
pushInterface(l,ret, typeof(Custom.IFoo));
return 2;
}
catch(Exception e) {
return error(l,e);
}
}
[SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
[UnityEngine.Scripting.Preserve]
static public int getItem(IntPtr l) {
try {
Custom self=(Custom)checkSelf(l);
string v;
checkType(l,2,out v);
var ret = self[v];
pushValue(l,true);
pushValue(l,ret);
return 2;
}
catch(Exception e) {
return error(l,e);
}
}
[SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
[UnityEngine.Scripting.Preserve]
static public int setItem(IntPtr l) {
try {
Custom self=(Custom)checkSelf(l);
string v;
checkType(l,2,out v);
int c;
checkType(l,3,out c);
self[v]=c;
pushValue(l,true);
return 1;
}
catch(Exception e) {
return error(l,e);
}
}
[UnityEngine.Scripting.Preserve]
static public void reg(IntPtr l) {
getTypeTable(l,"Custom");
addMember(l,getTypeName);
addMember(l,getInterface);
addMember(l,getItem);
addMember(l,setItem);
addMember(l,Custom.instanceCustom,true);
addMember(l,Custom.staticCustom,false);
createTypeMetatable(l,null, typeof(Custom),typeof(UnityEngine.MonoBehaviour));
}
}

----------

public static void getTypeTable(IntPtr l, string t)
{
newTypeTable(l, t);
// for static
LuaDLL.lua_newtable(l);
// for instance
LuaDLL.lua_newtable(l);
}

正如前面的注释,有MonoPInvokeCallbackAttribute 修饰符的两个函数没有再生成代码,而是直接调用addMember 注册了:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
protected static void addMember(IntPtr l, LuaCSFunction func, bool instance)
{
checkMethodValid(func);

pushValue(l, func);
string name = func.Method.Name;
LuaDLL.lua_setfield(l, instance ? -2 : -3, name);
}
public static void pushValue(IntPtr l, LuaCSFunction f)
{
LuaState.pushcsfunction (l, f);
}

//Lua_State constructor

----------

LuaDLL.luaL_openlibs(L);

string PCallCSFunction = @"
local assert = assert
local function check(ok,...)
assert(ok, ...)
return ...
end
return function(cs_func)
return function(...)
return check(cs_func(...))
end
end
";

LuaDLL.lua_dostring(L, PCallCSFunction);
//在当前栈索引t处的元素是一个table(这里就是注册表), 在该table中创建一个对象, 对象是当前栈顶的元素,
//并返回创建对象在表中的索引值, 之后会pop栈顶的对象; (即将栈顶元素放到t对应的table中)
//源码分析在此:https://blog.csdn.net/bbhe_work/article/details/51064132
PCallCSFunctionRef = LuaDLL.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);

----------

static public void pushcsfunction(IntPtr L, LuaCSFunction function)
{
LuaDLL.lua_getref(L, get(L).PCallCSFunctionRef);
//此时栈顶的内容为
//function(cs_func)
//return function(...)
// return check(cs_func(...))
//end
//lua_pushcclosure 实际上做的是压入参数 cs_func
LuaDLL.lua_pushcclosure(L, function, 0);
//然后调用该函数,传入一个参数,返回一个返回值
LuaDLL.lua_call(L, 1, 1);
//此时栈顶的值为
//function(...)
// return check(cs_func(...))
//end
//然后通过后面的 lua_setfield 存储到创建好的 table 中
}

其中lua_getref 和 lua_pushcclosure在Lua 5.1 中的源码定义如下(顺便分析下源码):

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
33
34
35
36
37
38
39
#define lua_getref(L,ref)       lua_rawgeti(L, LUA_REGISTRYINDEX, (ref))
//把 t[n] 的值压栈, 这里的 t 是指给定索引 index 处的一个值。 这是一个直接访问;就是说,它不会触发元方法。
LUA_API void lua_rawgeti (lua_State *L, int idx, int n) {
StkId o;
lua_lock(L);
//获取 idx 位置的栈中的内容
o = index2adr(L, idx);
//检查是否是 table
api_check(L, ttistable(o));
//获取 table 中 n 对应的 value 并赋值到栈顶
setobj2s(L, L->top, luaH_getnum(hvalue(o), n));
//栈顶指针++
api_incr_top(L);
lua_unlock(L);
}
LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {
Closure *cl;
lua_lock(L);
//创建一个 sizeof(CClosure) + (n - 1) * sizeof(TValue)大小的内存, 这段内存是 CClosure + TValue[n], 并做gc簿记
luaC_checkGC(L);
//检查栈空间足够
api_checknelems(L, n);
//新建一个闭包并设置env
cl = luaF_newCclosure(L, n, getcurrenv(L));
//绑定函数
cl->c.f = fn;
//栈顶指针下移 n
L->top -= n;
// 把栈上的n个元素赋值到c->upvalue[]数组中, 顺序是越先入栈的值放在upvalue数组的越开始位置,
// c->nupvalues指定改闭包upvalue的个数,赋值后会通过checkliveness进行内存回收
while (n--)
setobj2n(L, &cl->c.upvalue[n], L->top+n);
//压入新建的Closure到当前栈顶指针位置
setclvalue(L, L->top, cl);
lua_assert(iswhite(obj2gco(cl)));
//栈顶指针++
api_incr_top(L);
lua_unlock(L);
}

闭包调用的具体分析可以参考这篇博客

此时,我们来分析下栈的结构:

-1 为 lua_call(L, 1, 1) 后压入的函数返回值

-2 为 for instance 函数的 table

-3 为 for static 函数的 table

昨天的 _s 函数的处理就是同理了

本文来自:https://blog.csdn.net/NotMz/article/details/79666208