xlua中,C#调用lua函数(delegate)

调用例子1

C#层用delegate接收lua层的函数时,需要把lua栈中的function转换为C#层的结构,最简单的调用就比如说从Global中获取函数。

--Test.lua
testFunc1 = function()
    print("无参无返回")
end
-- c#
CustomCall1 customCall1 = LuaManager.GetInstance().Global.Get<CustomCall1>("testFunc1");

调用例子2

xlua自动生成(Gen)的wrap,也就是接收Lua参数的地方,只能取到lua函数,而C#层用于接收这个函数的,大部分是delegate,但是delegate无法直接保存lua函数,就需要一些操作来转换下。考虑如下:

//原函数签名
public static uint PostEvent(uint in_eventID, UnityEngine.GameObject in_gameObjectID, uint in_uFlags, AkCallbackManager.EventCallback in_pfnCallback, object in_pCookie, uint in_cExternals, AkExternalSourceInfoArray in_pExternalSources, uint in_PlayingID)
//lua调用
ak_sound_engine.PostEvent(id, go, 1, (self, &quot;on_event_finish&quot;), nil)
//生成的wrap
AkCallbackManager.EventCallback _in_pfnCallback = translator.GetDelegate&lt;AkCallbackManager.EventCallback&gt;(L, 4);
uint gen_ret = AkSoundEngine.PostEvent( _in_eventID, _in_gameObjectID, _in_uFlags, _in_pfnCallback, _in_pCookie );

不难发现,通过translator.GetObject方法,将lua栈中传来的函数包装成委托发送给了实际的函数

public T GetDelegate<T>(RealStatePtr L, int index) where T :class
{
  if (LuaAPI.lua_isfunction(L, index))
  {
    return CreateDelegateBridge(L, typeof(T), index) as T;
  }
  else if (LuaAPI.lua_type(L, index) == LuaTypes.LUA_TUSERDATA)
  {
    return (T)SafeGetCSObj(L, index);
  }
  else
  {
    return null;
  }
}

这里暂时只考虑传递过来的函数是lua函数的情况。初次访问的代码如下:

//简化版,初次访问
public object CreateDelegateBridge(RealStatePtr L, Type delegateType, int idx){
    LuaAPI.lua_pushvalue(L, idx);//委托的那个lua函数
    int reference = LuaAPI.luaL_ref(L);//xlua定义中,表t直接就是注册表了
    //上面两行的作用是把表lua函数加入到注册表中,并且返回其在注册表中的索引
    //上面两行是ref = 函数,后面三行是函数 = ref。
    //也就是说既可以通过函数找ref,也可以通过ref找函数
    LuaAPI.lua_pushvalue(L, idx);
    LuaAPI.lua_pushnumber(L, reference);
    LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX);
    DelegateBridgeBase bridge;
    bridge = new DelegateBridge(reference, luaEnv);//将lua函数的reference加入到bridge对象
    var ret = getDelegate(bridge, delegateType);
    bridge.AddDelegate(delegateType, ret);
    delegate_bridges[reference] = new WeakReference(bridge);
    return ret;
}
Delegate getDelegate(DelegateBridgeBase bridge, Type delegateType)
{
    Delegate ret = bridge.GetDelegateByType(delegateType);

    if (ret != null)
    {
        return ret;
    }
}
//DelegatesGensBridge.cs -- 自动生成的
public override Delegate GetDelegateByType(Type type)
{

    if (type == typeof(FpUI.FpImageGuideMask.IsRaycastValid))
    {
        return new FpUI.FpImageGuideMask.IsRaycastValid(__Gen_Delegate_Imp0);
    }

    return null;
}


public void AddDelegate(Type key, Delegate value)
{
    if (key == firstKey)
    {
        throw new ArgumentException(&quot;An element with the same key already exists in the dictionary.&quot;);
    }

    if (firstKey == null &amp;&amp; bindTo == null) // nothing
    {
        firstKey = key;
        firstValue = value;
    }
    else if (firstKey != null &amp;&amp; bindTo == null) // one key existed
    { //如果一个lua函数对应多个委托
        bindTo = new Dictionary<;Type, Delegate>;();
        bindTo.Add(firstKey, firstValue);
        firstKey = null;
        firstValue = null;
        bindTo.Add(key, value);
    }
    else
    {
        bindTo.Add(key, value);
    }
}

如上代码,前五行的LuaAPI将lua函数放入了注册表并且和ref一一对应,而后根据这个唯一对应lua函数的ref创建了bridge(也就是说,每一个bridge都一一对应了一个lua层的函数)
而后就可以根据委托的类型,根据自动生成的该委托实例的代码,生成一个委托(getDelegate(bridge, delegateType);) 然后将这个委托加入到bridge并且弱引用表指向它。

若之前根据这个相同的lua函数生成过bridge了,那就不需要再次生成了可以复用已有的bridge,代码如下:

public object CreateDelegateBridge(RealStatePtr L, Type delegateType, int idx)
{
    LuaAPI.lua_pushvalue(L, idx);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); //根据函数找ref
    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
            {
                exist_delegate = getDelegate(exist_bridge, delegateType);
                exist_bridge.AddDelegate(delegateType, exist_delegate);
                return exist_delegate;
            }
        }
    }
    else
    {
        LuaAPI.lua_pop(L, 1);//弹出push的idx
    }
}

从注册表查找,如果之前生成过bridge,则复用之前的。(如果弱引用被回收了,则还是走首次生成的逻辑。不过这里我有疑问,什么时候清的它的注册表?)然后通过类型,从这个bridge中获得这个类型的delegate,如果没有就新生成一个加入。

再来仔细看下自动生成的委托实例

public bool __Gen_Delegate_Imp0(UnityEngine.Vector2 p0)
{
#if THREAD_SAFE || HOTFIX_ENABLE
    lock (luaEnv.luaEnvLock)
    {
#endif
        RealStatePtr L = luaEnv.rawL;
        int errFunc = LuaAPI.pcall_prepare(L, errorFuncRef, luaReference);
        ObjectTranslator translator = luaEnv.translator;
        translator.PushUnityEngineVector2(L, p0);
        
        PCall(L, 1, 1, errFunc);

        
        bool __gen_ret = LuaAPI.lua_toboolean(L, errFunc + 1);
        LuaAPI.lua_settop(L, errFunc - 1);
        return  __gen_ret;
#if THREAD_SAFE || HOTFIX_ENABLE
    }
#endif
}
LUA_API int pcall_prepare(lua_State *L, int error_func_ref, int func_ref) {
    lua_rawgeti(L, LUA_REGISTRYINDEX, error_func_ref);
    lua_rawgeti(L, LUA_REGISTRYINDEX, func_ref);
    return lua_gettop(L) - 1;
}
public void PCall(IntPtr L, int nArgs, int nResults, int errFunc)
{
    if (LuaAPI.lua_pcall(L, nArgs, nResults, errFunc) != 0)
        luaEnv.ThrowExceptionFromError(errFunc - 1);
}

在这里,根据delegate中存储的ref找到了lua函数,并且通过注册表获得了这个函数并且放入了栈顶。并且后面压入了参数,使用PCall进行调用。LuaAPI.lua_toboolean(L, errFunc + 1);获取返回值,并用LuaAPI.lua_settop(L, errFunc – 1);清理栈

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

大纲

Share the Post:
滚动至顶部