沈玉彪 發表於 2021-9-24 09:59:00

深入xLua实现原理之C#如何调用Lua

<p>本文主要是探讨xLua下C#调用Lua的实现原理,有关Lua如何调用C#的介绍可以查看深入xLua实现原理之Lua如何调用C#</p>
<h3 id="c与lua数据通信机制">C#与Lua数据通信机制</h3>
<p>无论是Lua调用C#,还是C#调用Lua,都需要一个通信机制,来完成数据的传递。而Lua本身就是由C语言编写的,所以它出生自带一个和C/C++的通信机制。</p>
<p>Lua和C/C++的数据交互通过栈进行,操作数据时,首先将数据拷贝到"栈"上,然后获取数据,栈中的每个数据通过索引值进行定位,索引值为正时表示相对于栈底的偏移索引,索引值为负时表示相对于栈顶的偏移索引,索引值以1或-1为起始值,因此栈顶索引值永远为-1, 栈底索引值永远为1 。 “栈"相当于数据在Lua和C/C++之间的中转地。每种数据都有相应的存取接口。</p>
<p>而C#可以通过P/Invoke方式调用Lua的dll,通过这个dll执行Lua的C API。换言之C#可以借助C/C++来与Lua进行数据通信。在xLua的LuaDLL.cs文件中可以找到许多DllImport修饰的数据入栈与获取的接口。</p>
<pre><code class="language-csharp">// LuaDLL.cs

public static extern void lua_pushnumber(IntPtr L, double number);


public static extern void lua_pushboolean(IntPtr L, bool value);


public static extern void xlua_pushinteger(IntPtr L, int value);


public static extern double lua_tonumber(IntPtr L, int index);


public static extern int xlua_tointeger(IntPtr L, int index);


public static extern uint xlua_touint(IntPtr L, int index);


public static extern bool lua_toboolean(IntPtr L, int index);
</code></pre>
<p>除了普通的值类型之外,Lua中比较特殊但又很常用的大概就是table和funciton这两种类型了,下面逐一来分析</p>
<h3 id="传递lua-table到c">传递Lua table到C#</h3>
<p>以TestXLua类为例来看Lua table是如何被传递的,TestXLua有一个LuaTable类型的静态变量,LuaTable是C#这边定义的一个类,封装了一些对Lua table的操作</p>
<pre><code class="language-csharp">// 注意,这里添加的LuaCallCSharp特性只是为了使xLua为其生成代码,不添加并不影响功能

public class TestXLua
{
    public static LuaTable tab;
}
</code></pre>
<p>在点击Generate Code之后,部分生成代码如下所示。为tab变量生成了对应的set和get包裹方法</p>
<pre><code class="language-csharp">// TestXLuaWrap.cs

static int _g_get_tab(RealStatePtr L)
{
    try {
      ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
      translator.Push(L, TestXLua.tab);
    } catch(System.Exception gen_e) {
      return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return 1;
}


static int _s_set_tab(RealStatePtr L)
{
    try {
      ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
      TestXLua.tab = (XLua.LuaTable)translator.GetObject(L, 1, typeof(XLua.LuaTable));
   
    } catch(System.Exception gen_e) {
      return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return 0;
}
</code></pre>
<p>为tab静态变量赋值一个Lua table,table中包含一个 num = 1 键值对</p>
<pre><code class="language-lua">-- Lua测试代码
local t = {
    num = 1
}
CS.TestXLua.tab = t
</code></pre>
<p>上述代码在赋值时,最终会调用到_s_set_tab包裹方法(具体原理可以查看这里),Lua这边调用_s_set_tab前,会先将参数table t压入到栈中,因此_s_set_tab内部需要通过translator.GetObject拿到这个table,并将其赋值给tab静态变量</p>
<pre><code class="language-csharp">// ObjectTranslator.cs
public object GetObject(RealStatePtr L, int index, Type type)
{
    int udata = LuaAPI.xlua_tocsobj_safe(L, index);

    if (udata != -1)
    {
      // 对C#对象的处理
      object obj = objects.Get(udata);
      RawObject rawObject = obj as RawObject;
      return rawObject == null ? obj : rawObject.Target;
    }
    else
    {
      if (LuaAPI.lua_type(L, index) == LuaTypes.LUA_TUSERDATA)
      {
            GetCSObject get;
            int type_id = LuaAPI.xlua_gettypeid(L, index);
            if (type_id != -1 &amp;&amp; type_id == decimal_type_id)
            {
                decimal d;
                Get(L, index, out d);
                return d;
            }
            Type type_of_struct;
            if (type_id != -1 &amp;&amp; typeMap.TryGetValue(type_id, out type_of_struct) &amp;&amp; type.IsAssignableFrom(type_of_struct) &amp;&amp; custom_get_funcs.TryGetValue(type, out get))
            {
                return get(L, index);
            }
      }
      return (objectCasters.GetCaster(type)(L, index, null));
    }
}
</code></pre>
<p>GetObject方法负责从栈中获取指定类型的对象,对于LuaTable类型是通过objectCasters.GetCaster获取转换器后,通过转换器函数转换得到</p>
<pre><code class="language-csharp">// ObjectTranslator.cs
public ObjectCast GetCaster(Type type)
{
    if (type.IsByRef) type = type.GetElementType();// 如果是按引用传递的,则使用引用的对象的type

    Type underlyingType = Nullable.GetUnderlyingType(type);
    if (underlyingType != null)
    {
      return genNullableCaster(GetCaster(underlyingType));
    }
    ObjectCast oc;
    if (!castersMap.TryGetValue(type, out oc))
    {
      oc = genCaster(type);
      castersMap.Add(type, oc);
    }
    return oc;
}
</code></pre>
<p>xLua已经默认在castersMap中为一些类型定义好了转换函数,其中就包括LuaTable类型</p>
<pre><code class="language-csharp">// ObjectCasters.cs
public ObjectCasters(ObjectTranslator translator)
{
    this.translator = translator;
    castersMap = charCaster;
    castersMap = sbyteCaster;
    castersMap = byteCaster;
    castersMap = shortCaster;
    castersMap = ushortCaster;
    castersMap = intCaster;
    castersMap = uintCaster;
    castersMap = longCaster;
    castersMap = ulongCaster;
    castersMap = getDouble;
    castersMap = floatCaster;
    castersMap = decimalCaster;
    castersMap = getBoolean;
    castersMap =getString;
    castersMap = getObject;
    castersMap)] = getBytes;
    castersMap = getIntptr;
    //special type
    castersMap = getLuaTable;
    castersMap = getLuaFunction;
}
</code></pre>
<p>LuaTable对应的转换函数是getLuaTable</p>
<pre><code class="language-csharp">// ObjectCasters.cs
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 &amp;&amp; obj is LuaTable) ? obj : null;
    }
    if (!LuaAPI.lua_istable(L, idx))
    {
      return null;
    }
    // 处理普通table类型
    LuaAPI.lua_pushvalue(L, idx);
    return new LuaTable(LuaAPI.luaL_ref(L), translator.luaEnv);
}
</code></pre>
<p>getLuaTable的主要逻辑是将idx处的table通过luaL_ref添加到Lua注册表中并得到指向该table的索引,然后创建LuaTable对象保存该索引。也就是说Lua table在C#这边对应的是LuaTable对象,它们之间通过一个索引关联起来,这个索引表示Lua table在Lua注册表中的引用,利用这个索引可以获取到Lua table。拿到Lua table后,就可以继续访问Lua table的内容了。</p>
<pre><code class="language-csharp">// CS测试代码
int num = TestXLua.tab.Get&lt;int&gt;("num");
</code></pre>
<p>对Lua table的访问操作都被封装在LuaTable的Get方法中</p>
<pre><code class="language-csharp">// LuaTable.cs
public TValue Get&lt;TValue&gt;(string key)
{
    TValue ret;
    Get(key, out ret);
    return ret;
}

// no boxing version get
public void Get&lt;TKey, TValue&gt;(TKey key, out TValue value)
{
#if THREAD_SAFE || HOTFIX_ENABLE
    lock (luaEnv.luaEnvLock)
    {
#endif
      var L = luaEnv.L;
      var translator = luaEnv.translator;
      int oldTop = LuaAPI.lua_gettop(L);
      LuaAPI.lua_getref(L, luaReference);// 通过luaReference获取到对应的table
      translator.PushByType(L, key);

      if (0 != LuaAPI.xlua_pgettable(L, -2))// 查询 表
      {
            string err = LuaAPI.lua_tostring(L, -1);
            LuaAPI.lua_settop(L, oldTop);
            throw new Exception("get field [" + key + "] error:" + err);
      }

      LuaTypes lua_type = LuaAPI.lua_type(L, -1);
      Type type_of_value = typeof(TValue);
      if (lua_type == LuaTypes.LUA_TNIL &amp;&amp; type_of_value.IsValueType())
      {
            throw new InvalidCastException("can not assign nil to " + type_of_value.GetFriendlyName());
      }

      try
      {
            translator.Get(L, -1, out value);// 获取栈顶的元素,即 表
      }
      catch (Exception e)
      {
            throw e;
      }
      finally
      {
            LuaAPI.lua_settop(L, oldTop);
      }
#if THREAD_SAFE || HOTFIX_ENABLE
    }
#endif
}
</code></pre>
<p>Get方法的主要逻辑是,先通过保存的索引luaReference获取到Lua table,然后通过xlua_pgettable将 表 的值压栈,最后通过translator.Get获取到栈顶值对应的对象</p>
<pre><code class="language-csharp">// ObjectTranslator.cs
public void Get&lt;T&gt;(RealStatePtr L, int index, out T v)
{
    Func&lt;RealStatePtr, int, T&gt; get_func;
    if (tryGetGetFuncByType(typeof(T), out get_func))
    {
      v = get_func(L, index);// 将给定索引处的值转换为{T}类型
    }
    else
    {
      v = (T)GetObject(L, index, typeof(T));
    }
}
</code></pre>
<p>同样的,xLua也在tryGetGetFuncByType中为一些基本类型预定义好了对应的对象获取方法,采取泛型方式,这样可以避免拆箱和装箱。在本例中获取的值 num = 1 是一个int类型,通过转换器函数xlua_tointeger即可获得。xlua_tointeger就是对Lua原生API lua_tointeger的一个简单封装</p>
<pre><code class="language-csharp">bool tryGetGetFuncByType&lt;T&gt;(Type type, out T func) where T : class
{
    if (get_func_with_type == null)
    {
      get_func_with_type = new Dictionary&lt;Type, Delegate&gt;()
      {
            {typeof(int), new Func&lt;RealStatePtr, int, int&gt;(LuaAPI.xlua_tointeger) },
            {typeof(double), new Func&lt;RealStatePtr, int, double&gt;(LuaAPI.lua_tonumber) },
            {typeof(string), new Func&lt;RealStatePtr, int, string&gt;(LuaAPI.lua_tostring) },
            {typeof(byte[]), new Func&lt;RealStatePtr, int, byte[]&gt;(LuaAPI.lua_tobytes) },
            {typeof(bool), new Func&lt;RealStatePtr, int, bool&gt;(LuaAPI.lua_toboolean) },
            {typeof(long), new Func&lt;RealStatePtr, int, long&gt;(LuaAPI.lua_toint64) },
            {typeof(ulong), new Func&lt;RealStatePtr, int, ulong&gt;(LuaAPI.lua_touint64) },
            {typeof(IntPtr), new Func&lt;RealStatePtr, int, IntPtr&gt;(LuaAPI.lua_touserdata) },
            {typeof(decimal), new Func&lt;RealStatePtr, int, decimal&gt;((L, idx) =&gt; {
                decimal ret;
                Get(L, idx, out ret);
                return ret;
            }) },
            {typeof(byte), new Func&lt;RealStatePtr, int, byte&gt;((L, idx) =&gt; (byte)LuaAPI.xlua_tointeger(L, idx) ) },
            {typeof(sbyte), new Func&lt;RealStatePtr, int, sbyte&gt;((L, idx) =&gt; (sbyte)LuaAPI.xlua_tointeger(L, idx) ) },
            {typeof(char), new Func&lt;RealStatePtr, int, char&gt;((L, idx) =&gt; (char)LuaAPI.xlua_tointeger(L, idx) ) },
            {typeof(short), new Func&lt;RealStatePtr, int, short&gt;((L, idx) =&gt; (short)LuaAPI.xlua_tointeger(L, idx) ) },
            {typeof(ushort), new Func&lt;RealStatePtr, int, ushort&gt;((L, idx) =&gt; (ushort)LuaAPI.xlua_tointeger(L, idx) ) },
            {typeof(uint), new Func&lt;RealStatePtr, int, uint&gt;(LuaAPI.xlua_touint) },
            {typeof(float), new Func&lt;RealStatePtr, int, float&gt;((L, idx) =&gt; (float)LuaAPI.lua_tonumber(L, idx) ) },
      };
    }
</code></pre>
<h3 id="传递lua-function到c">传递Lua function到C#</h3>
<p>Lua的function传递到C#后,对应的是C#的委托,同样以TestXLua类为例来分析具体过程</p>
<pre><code class="language-csharp">// 注意,这里添加的LuaCallCSharp特性只是为了使xLua为其生成代码,不添加并不影响功能

public class TestXLua
{
   
    public delegate int Func(string s, bool b, float f);

    public static Func func;
}
</code></pre>
<p>点击Generate Code后,生成的部分TestXLuaWrap代码如下所示。为func变量生成了对应的set和get包裹方法</p>
<pre><code class="language-csharp">// TestXLuaWrap.cs

static int _g_get_func(RealStatePtr L)
{
    try {
      ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
      translator.Push(L, TestXLua.func);
    } catch(System.Exception gen_e) {
      return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return 1;
}


static int _s_set_func(RealStatePtr L)
{
    try {
      ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
      TestXLua.func = translator.GetDelegate&lt;TestXLua.Func&gt;(L, 1);
   
    } catch(System.Exception gen_e) {
      return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return 0;
}
</code></pre>
<p>为func静态变量赋值一个Lua function</p>
<pre><code class="language-lua">-- Lua测试代码
CS.TestXLua.func = function(s, b, i)
   
end
</code></pre>
<p>上述代码在赋值时,会最终调用_s_set_func包裹方法(具体原理可以查看这里),Lua在调用_s_set_func前,会将参数function压入到栈中,因此_s_set_func内部需要通过translator.GetDelegate拿到这个function,并将其赋值给func静态变量</p>
<pre><code class="language-csharp">// ObjectTranslator.cs
public T GetDelegate&lt;T&gt;(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;
    }
}
</code></pre>
<p>对于Lua function类型会通过CreateDelegateBridge创建一个对应的委托并返回。CreateDelegateBridge内部会创建一个DelegateBridge对象来对应Lua function,原理和LuaTable类似,也是通过一个索引保持联系,利用这个索引可以获取到Lua function</p>
<pre><code class="language-csharp">// ObjectTranslator.cs
Dictionary&lt;int, WeakReference&gt; delegate_bridges = new Dictionary&lt;int, WeakReference&gt;();// 弱引用创建的DelegateBridge
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.IsAlive)
      {
            if (delegateType == null)
            {
                return delegate_bridges.Target;
            }
            DelegateBridgeBase exist_bridge = delegate_bridges.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);
    }

    LuaAPI.lua_pushvalue(L, idx);
    int reference = LuaAPI.luaL_ref(L);// 将idx处的元素添加到Lua注册表中
    LuaAPI.lua_pushvalue(L, idx);
    LuaAPI.lua_pushnumber(L, reference);
    LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX);// 注册表 = reference
    DelegateBridgeBase bridge;
    try
    {
#if (UNITY_EDITOR || XLUA_GENERAL) &amp;&amp; !NET_STANDARD_2_0
      if (!DelegateBridge.Gen_Flag)
      {
            bridge = Activator.CreateInstance(delegate_birdge_type, new object[] { reference, luaEnv }) as DelegateBridgeBase;// 使用反射创建DelegateBridge对象
      }
      else
#endif
      {
            bridge = new DelegateBridge(reference, luaEnv);
      }
    }
    catch(Exception e)
    {
      LuaAPI.lua_pushvalue(L, idx);
      LuaAPI.lua_pushnil(L);
      LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX);
      LuaAPI.lua_pushnil(L);
      LuaAPI.xlua_rawseti(L, LuaIndexes.LUA_REGISTRYINDEX, reference);
      throw e;
    }
    if (delegateType == null)
    {
      delegate_bridges = new WeakReference(bridge);
      return bridge;
    }
    try {
      var ret = getDelegate(bridge, delegateType);// 通过bridge获取到指定类型的委托
      bridge.AddDelegate(delegateType, ret);
      delegate_bridges = new WeakReference(bridge);
      return ret;
    }
    catch(Exception e)
    {
      bridge.Dispose();
      throw e;
    }
}
</code></pre>
<p>在取得DelegateBridge对象后,还需要通过getDelegate方法,获取delegateType类型的委托,即C#这边指定要接收Lua function时声明的委托类型。在本例中是typeof(TestXLua.Func)</p>
<pre><code class="language-csharp">Delegate getDelegate(DelegateBridgeBase bridge, Type delegateType)
{
    // ...
    Func&lt;DelegateBridgeBase, Delegate&gt; delegateCreator;
    if (!delegateCreatorCache.TryGetValue(delegateType, out delegateCreator))
    {
      // get by parameters
      MethodInfo delegateMethod = delegateType.GetMethod("Invoke");
      // 生成代码为配置了 CSharpCallLua的委托 生成以__Gen_Delegate_Imp开头的方法 并添加到 DelegateBridge 类中
      var methods = bridge.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m =&gt; !m.IsGenericMethodDefinition &amp;&amp; (m.Name.StartsWith("__Gen_Delegate_Imp") || m.Name == "Action")).ToArray();
      // 查找bridge中与delegateMethod匹配的方法,这个方法必须是以__Gen_Delegate_Imp或Action开头
      for (int i = 0; i &lt; methods.Length; i++)
      {
            if (!methods.IsConstructor &amp;&amp; Utils.IsParamsMatch(delegateMethod, methods))
            {
                var foundMethod = methods;
                delegateCreator = (o) =&gt;
#if !UNITY_WSA || UNITY_EDITOR
                  Delegate.CreateDelegate(delegateType, o, foundMethod);// 创建表示foundMethod的delegateType类型的委托
#else
                  foundMethod.CreateDelegate(delegateType, o);
#endif
                break;
            }
      }

      if (delegateCreator == null)
      {
            delegateCreator = getCreatorUsingGeneric(bridge, delegateType, delegateMethod);
      }
      delegateCreatorCache.Add(delegateType, delegateCreator);
    }

    ret = delegateCreator(bridge);// 创建委托
    if (ret != null)
    {
      return ret;
    }

    throw new InvalidCastException("This type must add to CSharpCallLua: " + delegateType.GetFriendlyName());
}
</code></pre>
<p>如何利用bridge获取到指定类型delegateType的委托呢?主要逻辑是,先获得delegateType委托的Invoke方法,然后通过反射遍历bridge类型的所有方法,找到与Invoke参数匹配的目标方法。再使用bridge实例与目标方法创建一个delegateType类型的委托。换言之,这个delegateType类型的委托绑定的是bridge的与之参数匹配的成员方法,而且这个方法名称要以"__Gen_Delegate_Imp"开头</p>
<p>用于接收Lua function的委托必须添加CSharpCallLua特性也正是因为要为其生成以"__Gen_Delegate_Imp"开头的方法,如果不添加则会抛出异常</p>
<pre><code> c# exception:System.InvalidCastException: This type must add to CSharpCallLua: TestXLua+Func
</code></pre>
<p>添加CSharpCallLua特性后,点击Generate Code,会为该委托生成如下代码。虽然代码生成在DelegatesGensBridge.cs文件中,但它通过partial声明为DelegateBridge类的一部分。生成的函数名均以"__Gen_Delegate_Imp"开头,且参数类型和个数与该委托一致</p>
<pre><code class="language-csharp">// DelegatesGensBridge.cs
public partial class DelegateBridge : DelegateBridgeBase
{
    // ...
    public int __Gen_Delegate_Imp1(string p0, bool p1, float p2)
    {
#if THREAD_SAFE || HOTFIX_ENABLE
      lock (luaEnv.luaEnvLock)
      {
#endif
            RealStatePtr L = luaEnv.rawL;
            int errFunc = LuaAPI.pcall_prepare(L, errorFuncRef, luaReference);
            
            LuaAPI.lua_pushstring(L, p0);// 压栈参数
            LuaAPI.lua_pushboolean(L, p1);// 压栈参数
            LuaAPI.lua_pushnumber(L, p2);// 压栈参数
            
            PCall(L, 3, 1, errFunc);// Lua function调用
            
            
            int __gen_ret = LuaAPI.xlua_tointeger(L, errFunc + 1);
            LuaAPI.lua_settop(L, errFunc - 1);
            return__gen_ret;
#if THREAD_SAFE || HOTFIX_ENABLE
      }
#endif
    }
}
</code></pre>
<p>TestXLua.Func类型委托绑定的就是这个生成函数__Gen_Delegate_Imp1。之所以要使用生成函数,是因为需要生成函数来完成参数的压栈与Lua function调用</p>
<p>为了正确的和Lua通讯,C函数已经定义好了协议。这个协议定义了参数以及返回值传递方法:C函数通过Lua中的栈来接受参数,参数以正序入栈(第一个参数首先入栈)。因此,当函数开始的时候,lua_gettop(L)可以返回函数收到的参数个数。第一个参数(如果有的话)在索引1的地方,而最后一个参数在索引lua_gettop(L)处。当需要向Lua返回值的时候,C函数只需要把它们以正序压到堆栈上(第一个返回值最先压入),然后返回这些返回值的个数。在这些返回值之下的,堆栈上的东西都会被Lua丢掉。和Lua函数一样,从Lua中调用C函数可以有很多返回值。</p>
<p>文章开头也已提到,C#可以借助C/C++来与Lua进行数据通信,所以C#在函数调用前,需要通过C API来压栈函数调用所需的参数,而这个逻辑就被封装在了以"__Gen_Delegate_Imp"开头的生成方法中。生成方法将参数压栈后,再通过PCall调用Lua function,PCall内部调用的就是Lua原生API lua_pcall</p>
<p>总结一下整个流程</p>
<pre><code class="language-lua">-- Lua测试代码
CS.TestXLua.func = function(s, b, i)
   
end
</code></pre>
<p>当为TestXLua.func赋值Lua function时,会触发func变量的set包裹方法_s_set_func,_s_set_func内部会获取一个委托设置给func变量。这个委托绑定的是DelegateBridge对象的以"__Gen_Delegate_Imp"开头的生成方法,DelegateBridge对象同时保存着Lua function的索引</p>
<pre><code class="language-csharp">// CS测试代码
TestXLua.func("test", false, 3);
</code></pre>
<p>当调用TestXLua.func时,相当于调用以"__Gen_Delegate_Imp"开头的生成方法,这个生成方法负责参数压栈,并通过保存的索引获取到Lua function,然后使用lua_pcall完成Lua function的调用</p>
<h3 id="gc">GC</h3>
<p>C#和Lua都是有自动垃圾回收机制的,并且相互是无感知的。如果传递到C#的Lua对象被Lua自动回收掉了,而C#这边仍毫不知情继续使用,则必然会导致无法预知的错误。所以基本原则是传递到C#的Lua对象,Lua不能自动回收,只能C#在确定不再使用后通知Lua进行回收</p>
<p>为了保证Lua不会自动回收对象,所有传递给C#的对象都会被Lua注册表引用。比如前面创建LuaTable或DelegateBridge时 都有调用LuaAPI.luaL_ref将对象添加到注册表中</p>
<p>C#这边为对应的Lua对象定义了LuaBase基类,LuaTable或DelegateBridge均派生于LuaBase,这个类实现了IDisposable接口,并且在析构函数中会调用Dispose</p>
<pre><code class="language-csharp">// LuaBase.cs
public virtual void Dispose(bool disposeManagedResources)
{
    if (!disposed)
    {
      if (luaReference != 0)
      {
#if THREAD_SAFE || HOTFIX_ENABLE
            lock (luaEnv.luaEnvLock)
            {
#endif
                bool is_delegate = this is DelegateBridgeBase;
                if (disposeManagedResources)
                {
                  luaEnv.translator.ReleaseLuaBase(luaEnv.L, luaReference, is_delegate);// 释放Lua对象
                }
                else //will dispse by LuaEnv.GC
                {
                  luaEnv.equeueGCAction(new LuaEnv.GCAction { Reference = luaReference, IsDelegate = is_delegate });// 加入GC队列
                }
#if THREAD_SAFE || HOTFIX_ENABLE
            }
#endif
      }
      disposed = true;
    }
}

</code></pre>
<p>当disposeManagedResources为true时,直接调用ReleaseLuaBase释放Lua对象</p>
<pre><code class="language-csharp">// ObjectTranslator.cs
public void ReleaseLuaBase(RealStatePtr L, int reference, bool is_delegate)
{
    if(is_delegate)
    {
      LuaAPI.xlua_rawgeti(L, LuaIndexes.LUA_REGISTRYINDEX, reference);
      if (LuaAPI.lua_isnil(L, -1))
      {
            LuaAPI.lua_pop(L, 1);
      }
      else
      {
            LuaAPI.lua_pushvalue(L, -1);
            LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
            if (LuaAPI.lua_type(L, -1) == LuaTypes.LUA_TNUMBER &amp;&amp; LuaAPI.xlua_tointeger(L, -1) == reference) //
            {
                //UnityEngine.Debug.LogWarning("release delegate ref = " + luaReference);
                LuaAPI.lua_pop(L, 1);// pop LUA_REGISTRYINDEX
                LuaAPI.lua_pushnil(L);
                LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX); // LUA_REGISTRYINDEX = nil
            }
            else //another Delegate ref the function before the GC tick
            {
                LuaAPI.lua_pop(L, 2); // pop LUA_REGISTRYINDEX &amp; func
            }
      }

      LuaAPI.lua_unref(L, reference);
      delegate_bridges.Remove(reference);
    }
    else
    {
      LuaAPI.lua_unref(L, reference);
    }
}
</code></pre>
<p>ReleaseLuaBase的主要任务是将Lua对象从Lua注册表中移除,这样Lua GC时发现该对象不再被引用,就可以进行回收了</p>
<pre><code class="language-csharp">// LuaEnv.cs
public void Tick()
{
#if THREAD_SAFE || HOTFIX_ENABLE
    lock (luaEnvLock)
    {
#endif
      var _L = L;
      lock (refQueue)
      {
            while (refQueue.Count &gt; 0)// 遍历GC队列
            {
                GCAction gca = refQueue.Dequeue();
                translator.ReleaseLuaBase(_L, gca.Reference, gca.IsDelegate);
            }
      }
#if !XLUA_GENERAL
      last_check_point = translator.objects.Check(last_check_point, max_check_per_tick, object_valid_checker, translator.reverseMap);
#endif
#if THREAD_SAFE || HOTFIX_ENABLE
    }
#endif
}
</code></pre>
<p>当disposeManagedResources为false时,会将其加入GC队列。当主动释放Lua环境时,会遍历GC队列,再逐一调用ReleaseLuaBase进行释放</p>
<h3 id="参考">参考</h3>
<ul>
<li>添加了中文注释的xLua源码</li>
<li>深入xLua实现原理之Lua如何调用C#</li>
</ul>


</div>
<div id="MySignature" role="contentinfo">
    <div>作者:iwiniwin</div>
<div>出处:http://www.cnblogs.com/iwiniwin/</div>
<div>本文为博主原创文章,转载请附上原文出处链接和本声明。</div><br><br>
来源:https://www.cnblogs.com/iwiniwin/p/15323970.html
頁: [1]
查看完整版本: 深入xLua实现原理之C#如何调用Lua