zoukankan      html  css  js  c++  java
  • c#调用lua

    一、最简单的LuaEnv的DoString方法。
    DoString(init_xlua, "Init");
    public object[] DoString(byte[] chunk, string chunkName = "chunk", LuaTable env = null)
        {
    #if THREAD_SAFE || HOTFIX_ENABLE
            lock (luaEnvLock)
            {
    #endif
                var _L = L;
                int oldTop = LuaAPI.lua_gettop(_L);
                int errFunc = LuaAPI.load_error_func(_L, errorFuncRef);
                if (LuaAPI.xluaL_loadbuffer(_L, chunk, chunk.Length, chunkName) == 0)
                {
                    if (env != null)
                    {
                        env.push(_L);
                        LuaAPI.lua_setfenv(_L, -2);
                    }
     
                    if (LuaAPI.lua_pcall(_L, 0, -1, errFunc) == 0)
                    {
                        LuaAPI.lua_remove(_L, errFunc);
                        return translator.popValues(_L, oldTop);
                    }
                }
                return null;
    #if THREAD_SAFE || HOTFIX_ENABLE
        }
    #endif
    }
    xua.c方法:
    LUALIB_API int xluaL_loadbuffer (lua_State *L, const char *buff, int size,
                                    const char *name) {
       return luaL_loadbuffer(L, buff, size, name);
    }
    只是调用lua的c方法lua_load 把一个编译好的代码块作为一个 Lua 函数压到栈顶,然后调用lua_pcall lua c方法执行方法(代码段生成的)。
    二、lua的table是如何转成c#的LuaTable类实例的?
    以LuaEnv.Global为例展示xlua是如何把lua_table(_G)表转成c#的LuaTable类实例的。
    if (0 != LuaAPI.xlua_getglobal(rawL, "_G"))
    {
        throw new Exception("get _G fail!");
    }
    translator.Get(rawL, -1, out _G);
    translator.Get会调用到objectCasters.GetCaster(打个断点就知道了),最终通过castersMap[typeof(LuaTable)] 调用到getLuaTable方法。
    castersMap在ObjectCasters的构造方法里填充的。
    private object getLuaTable(RealStatePtr L, int idx, object target)
    {
        if (LuaAPI.lua_type(L, idx) == LuaTypes.LUA_TUSERDATA)
        {
            object obj = translator.SafeGetCSObj(L, idx);
            return (obj != null && obj is LuaTable) ? obj : null;
        }
        if (!LuaAPI.lua_istable(L, idx))
        {
            return null;
        }
        LuaAPI.lua_pushvalue(L, idx);
        return new LuaTable(LuaAPI.luaL_ref(L), translator.luaEnv);
    }
    如上:lua_table转成c#的LuaTable步骤有
    1.把要转成LuaTable的lua_table压栈。
    2.调用LuaAPI.luaL_ref(L)获取指向该lua_table的唯一id。
    3.新建LuaTable并保存该唯一引用。
    最终,c#只保存lua_table的引用id,真正的对表操作是在c里面实现的
    上面的步骤每执行一次,也就是每次获取lua_table都要新建一个LuaTable引用实例,都需要在堆上分配空间。而频繁的分配堆内存可能会引发GC,而GC其实是很耗时的。
     
    对.Net GC不是很了解的可以参考:https://zhuanlan.zhihu.com/p/38799766
    三、c#调用lua function经历了哪些步骤?
    1.通过LuaFunction调用。
    luaEnv.Global是我们上一步新建的LuaTable类,它的luaReference指向了lua的_G全局表。
    1.以获取luaEnv.Global中的方法为例,流程大概是:
    1.通过luaEnv.Global的luaReference在xlua.c中把_G全局表压栈。
    2.调用LuaAPI.xlua_pgettable_bypath方法在_G中获取名为GameMain表并压栈。
    3.在压栈的GameMain中通过lua_gettable获取到名为OnLevelWasLoaded的lua方法并压栈。
    4.调用LuaAPI.luaL_ref(L)获取指向该方法的引用id ref_id。
    5.通过ref_id创建LuaFunction方法。
     
    瓶颈也是一样的,每次调用都要进行lua的查表、生成新的LuaFunction。
    private object getLuaFunction(RealStatePtr L, int idx, object target)
    {
        if (LuaAPI.lua_type(L, idx) == LuaTypes.LUA_TUSERDATA)
        {
            object obj = translator.SafeGetCSObj(L, idx);
            return (obj != null && obj is LuaFunction) ? obj : null;
        }
        if (!LuaAPI.lua_isfunction(L, idx))
        {
            return null;
        }
        LuaAPI.lua_pushvalue(L, idx);
        return new LuaFunction(LuaAPI.luaL_ref(L), translator.luaEnv);
    }
    2.调用LuaFunction。
    直接调用func.call方法就可以了。
    int errFunc = LuaAPI.load_error_func(L, luaEnv.errorFuncRef);
    LuaAPI.lua_getref(L, luaReference);//lua_function压栈。
    if (args != null)
    {
        nArgs = args.Length;
        for (int i = 0; i < args.Length; i++)
        {
            translator.PushAny(L, args[i]);//参数压栈
        }
    }
    int error = LuaAPI.lua_pcall(L, nArgs, -1, errFunc);//调用
    2.通过生成委托适配代码调用。
    以luaEnv.Global.GetInPath<Action<int>>("GameMain.OnLevelWasLoaded")为例,展示xlua是如何通过委托实现lua_function的调用的。
    流程大致如下:
    1.调用到objectCasters.GetCaster获取lua_function对应的Bridge实例。
    2.调用到CreateDelegateBridge,如果之前缓存过,走缓存,否则跳转3。
    public object CreateDelegateBridge(RealStatePtr L, Type delegateType, int idx)
    {
        LuaAPI.lua_pushvalue(L, idx);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
        if (!LuaAPI.lua_isnil(L, -1))//之前加载过,走缓存
        {
            int referenced = LuaAPI.xlua_tointeger(L, -1);
            LuaAPI.lua_pop(L, 1);
     
            if (delegate_bridges[referenced].IsAlive)
            {
                if (delegateType == null)
                {
                    return delegate_bridges[referenced].Target;
                }
                DelegateBridgeBase exist_bridge = delegate_bridges[referenced].Target as DelegateBridgeBase;
                Delegate exist_delegate;
                if (exist_bridge.TryGetDelegate(delegateType, out exist_delegate))
                {
                    return exist_delegate;
                }
                ...
            }
        }
        else //第一次加载
        {
            LuaAPI.lua_pushvalue(L, idx);
            int reference = LuaAPI.luaL_ref(L);
            LuaAPI.lua_pushvalue(L, idx);
            LuaAPI.lua_pushnumber(L, reference);
            LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX);
            //栈:lua_func
            //register[lua_func] = ref_id
            DelegateBridgeBase bridge;
            bridge = new DelegateBridge(reference, luaEnv);//省略一部分,最终走的是这边,每个lua_function都会是生成一个bridge,并把对应的ref_id赋值给bridge. reference
            try {
                var ret = getDelegate(bridge, delegateType);
                bridge.AddDelegate(delegateType, ret);
                delegate_bridges[reference] = new WeakReference(bridge);
                return ret;
            }
        }
    }
    3.在getDelegate调用DelegateBridge. GetDelegateByType工厂方法,生成一个新的委托方法。
    Delegate getDelegate(DelegateBridgeBase bridge, Type delegateType)
    {
        Delegate ret = bridge.GetDelegateByType(delegateType);
     
        if (ret != null)
        {
            return ret;
        }
        ...//忽略特殊情况
    }
    4.最终返回的是DelegatesGensBridge文件生成的对应的c#委托,例如Action<int>。
    if (type == typeof(System.Action<int>))
    {
        return new System.Action<int>(__Gen_Delegate_Imp2);//创建新的Action并返回。
    }
     
    public void __Gen_Delegate_Imp2(int p0)
    {
    #if THREAD_SAFE || HOTFIX_ENABLE
        lock (luaEnv.luaEnvLock)
        {
    #endif
            RealStatePtr L = luaEnv.rawL;
            int errFunc = LuaAPI.pcall_prepare(L, errorFuncRef, luaReference);//方法压栈           
            LuaAPI.xlua_pushinteger(L, p0);  //参数压栈             
            PCall(L, 1, 0, errFunc);                         
            LuaAPI.lua_settop(L, errFunc - 1); //lua方法调用            
    #if THREAD_SAFE || HOTFIX_ENABLE
        }
    #endif
    }
    这边的luaReference是指向栈顶的lua_function的指针,他通过
    bridge = new DelegateBridge(reference, luaEnv);赋值。
    继承关系:DelegateBridge->DelegateBridgeBase->LuaBase。luaReference在LuaBase的构造方法里赋值,errorFuncRef = luaenv.errorFuncRef在DelegateBridgeBase赋值。
    每个lua_function都会生成对应的DelegateBridge实例(这个实例做了缓存,每个lua_function只会执行一次)。我们应该对要频繁引用的lua方法尽量做缓存,以避免频繁实例化DelegateBridge过程。
    5.调用。
    例如:调用下面的sceneLoad实际是调用DelegatesGensBridge的__Gen_Delegate_Imp2方法,首先该方法把luaReference指向的lua_function压栈,然后把int型参数p0压栈,在通过PCall方法进行lua方法lua_function的调用。
    Action<int> sceneLoad;
    sceneLoad = luaEnv.Global.GetInPath<Action<int>>("GameMain.OnLevelWasLoaded");//实际赋值的是__Gen_Delegate_Imp2方法。
    sceneLoad(level);//调用__Gen_Delegate_Imp2方法
    3.两种方式比较:
    xlua是推荐使用委托方式的,因为委托是类型安全的,而且避免了值类型的装箱操作。
    映射到delegate:这种是建议的方式,性能好很多,而且类型安全。缺点是要生成代码。
    映射到LuaFunction:这种方式的优缺点刚好和第一种相反。
    但是就算是使用委托,每个lua方法也要生成对应的bridge,再通过GetDelegateByType的一长串的if判断,最终还要new一个委托出来。
     
    所以xlua的建议是:访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
     
    其实,c#对lua_table,lua_function的引用只是持有了它们的ref_id,通过这个ref_id可以在c里面找到对应的lua_table,lua_function。这两种方式本质的区别在于委托对参数进行了包装,避免了值类型的装箱、拆箱操作。
    4.调用错误时的错误日志:DelegateBridge.PCall
    public void PCall(IntPtr L, int nArgs, int nResults, int errFunc)
    {
        if (LuaAPI.lua_pcall(L, nArgs, nResults, errFunc) != 0)
            luaEnv.ThrowExceptionFromError(errFunc - 1);
    }
     
    public void ThrowExceptionFromError(int oldTop)
            {
    #if THREAD_SAFE || HOTFIX_ENABLE
                lock (luaEnvLock)
                {
    #endif
                    object err = translator.GetObject(L, -1);
                    LuaAPI.lua_settop(L, oldTop);
     
                    // A pre-wrapped exception - just rethrow it (stack trace of InnerException will be preserved)
                    Exception ex = err as Exception;
                    if (ex != null) throw ex;
     
                    // A non-wrapped Lua error (best interpreted as a string) - wrap it and throw it
                    if (err == null) err = "Unknown Lua Error";
                    throw new LuaException(err.ToString());
    #if THREAD_SAFE || HOTFIX_ENABLE
                }
    #endif
     
    四、获取lua其余类型参数。
     
  • 相关阅读:
    b_bd_序列合并(k路归并思想)
    b_bd_完成括号匹配(记录左括号数)
    b_zj_用户喜好(逆向思维记录喜好值的下标+二分查找)
    Bean的自动装配 | 使用注解实现
    bean的作用域【spring基础】
    DI 依赖注入(Dependency Injection)【spring基础】
    Spring创建对象
    IoC 理论推导 与 解释【Spring基础】
    Spring简介
    【1s 最 简单解决】MyBatis错误 -1 字节的 UTF-8 序列的字节 1 无效
  • 原文地址:https://www.cnblogs.com/wang-jin-fu/p/13508804.html
Copyright © 2011-2022 走看看