SLua入门学习(一):SLua的C#函数导出

发表于2018-04-23
评论0 3.7k浏览
前面介绍了热更新原理及第一个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

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引