1. 程式人生 > 實用技巧 >c#呼叫lua

c#呼叫lua

目錄:Xlua原始碼學習 一、最簡單的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 https://www.jianshu.com/p/d71bb3e50e1e 三、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。這兩種方式本質的區別在於委託對引數進行了包裝,避免了值型別的裝箱、拆箱操作。 四、獲取lua其餘型別引數。