SLua入门学习(一):SLua的C#函数导出
发表于2018-04-23
前面介绍了热更新原理及第一个SLua DEMO,本篇就继续Slua 的第二个Demo, 这个Demo 演示的主要是将 c# 函数注入到 lua 中并在 lua 中调用,一起来看看吧。
// 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 中找到
[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 代码, 我们直接看新生成的代码:
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 注册了:
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 中的源码定义如下(顺便分析下源码):
#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