xLua源码解析——C#调用Lua
概述开始分析前,我们可以结合xLua的Tutorial->CSCallLua.cs看,C#调用Lua,主要分为以下4种情况:
[*]获取Lua的number、string、boolean
[*]获取原来在C#实例化的对象。
[*]获取table,映射为LuaTable、类、接口、数组、泛型List或者Dictionary。
[*]获取function,映射为LuaFunction、Delegate(包括Action、Func等)。
调用流程
C#访问Lua,只能通过LuaEnv的Global入手,其中Global是LuaTable类型的全局表,例如:
luaenv.Global.Get<int>(&#34;a&#34;);
luaenv.Global.Get<string>(&#34;b&#34;)
luaenv.Global.Get<bool>(&#34;c&#34;)
所以要了解C#调用Lua,就先从LuaTable的TValue Get<TValue>(string key)开始。
下面的Get方法比较简单,就是把即将要访问的table和key先压入栈,并取出value放在栈上,并初步判断合法性。
public void Get<TKey, TValue>(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);// 压入引用的table,luaReference是lua全局引用表的索引值
translator.PushByType(L, key);// 栈压入key
if (0 != LuaAPI.xlua_pgettable(L, -2))// 调用:table
{
string err = LuaAPI.lua_tostring(L, -1);
LuaAPI.lua_settop(L, oldTop);
throw new Exception(&#34;get field [&#34; + key + &#34;] error:&#34; + err);
}
LuaTypes lua_type = LuaAPI.lua_type(L, -1);
Type type_of_value = typeof(TValue);
if (lua_type == LuaTypes.LUA_TNIL && type_of_value.IsValueType())
{
// 取出了nil,但是要获取的是值类型
throw new InvalidCastException(&#34;can not assign nil to &#34; + type_of_value.GetFriendlyName());
}
try
{
translator.Get(L, -1, out value);// 真正去取值到value
}
catch (Exception e)
{
throw e;
}
finally
{
LuaAPI.lua_settop(L, oldTop);
}
#if THREAD_SAFE || HOTFIX_ENABLE
}
#endif
}
c函数xlua_pgettable,其实就是对lua_gettable的调用封装,有兴趣可以看看下面的源码。
static int c_lua_gettable(lua_State* L) {
lua_gettable(L, 1);
return 1;
}
LUA_API int xlua_pgettable(lua_State* L, int idx) {
int top = lua_gettop(L);
idx = lua_absindex(L, idx);
lua_pushcfunction(L, c_lua_gettable);
lua_pushvalue(L, idx);
lua_pushvalue(L, top);
lua_remove(L, top);
return lua_pcall(L, 2, 1, 0);
}在上面取值后,就要进行转换了,转换逻辑在translator.Get(L, -1, out value);,先看源码:
// ObjectTranslator.Get
public void Get<T>(RealStatePtr L, int index, out T v)
{
Func<RealStatePtr, int, T> get_func;
// 获取配置好的转换函数(只有c#基础类型,例如int、float、string、byte等等)
if (tryGetGetFuncByType(typeof(T), out get_func))
{
v = get_func(L, index);
}
else
{
v = (T)GetObject(L, index, typeof(T));
}
}
tryGetGetFuncByType实际上就是最前面提到的情况1,获取Lua的基础类型,对应C#的基础类型,需要注意的是,转换函数都是通过c实现的,具体就不贴代码了。
重点看看其他类型的转换,在GetObject方法,主要在处理情况2,判断value是否是C#的实例。
public object GetObject(RealStatePtr L, int index, Type type)
{
// 是否是c#引用类型的实例?
// 参考《Lua调用C#》c#的实例创建都会在lua层记录实例在缓存列表的索引。
int udata = LuaAPI.xlua_tocsobj_safe(L, index);
if (udata != -1)
{
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;
// 获取UserData的具体类型id
int type_id = LuaAPI.xlua_gettypeid(L, index);
// Decimal类型
if (type_id != -1 && type_id == decimal_type_id){
decimal d;
Get(L, index, out d);
return d;
}
// struct类型
Type type_of_struct;
if (type_id != -1
&& typeMap.TryGetValue(type_id, out type_of_struct)
&& type.IsAssignableFrom(type_of_struct)
&& custom_get_funcs.TryGetValue(type, out get))
{
return get(L, index);
}
}
// 其他类型找转换函数并执行
return (objectCasters.GetCaster(type)(L, index, null));
}
}
如果看过《Lua调用C#》这一节的应该知道,C#的对象传入Lua,实际上传递的是C#侧缓存列表(objects)的索引,所以能取到索引,就能立刻得到C#的对象。这里先贴一下xlua_tocsobj_safe源码:
// 根据索引获取udata(udata是实例在c#侧缓存列表的索引值)
LUA_API int xlua_tocsobj_safe(lua_State *L,int index) {
int *udata = (int *)lua_touserdata (L,index);// 根据索引取值,转换为userdata
if (udata != NULL) {
if (lua_getmetatable(L,index)) {// 获取userdata的metatable
lua_pushlightuserdata(L, &tag);// 传入tag = 0
lua_rawget(L,-2);// metatable
if (!lua_isnil (L,-1)) { // metatable != nil 表示是c#的实例
lua_pop (L, 2);// 弹出metatable和tag
return *udata;
}
lua_pop (L, 2);
}
}
return -1;
}如果没有索引,但是又是userdata类型,则可以看看是否是Decimal或者struct类型,因为struct是值类型,所以不会像类一样保存在缓存列表,这里注意custom_get_funcs列表,里面记录的是生成代码时候产生的类和struct的Get方法。
接下来看GetCaster方法,这个方法前半部分用于处理一些特殊的类型,获取最终要转换的类型,结合注释看:
public ObjectCast GetCaster(Type type)
{
// 是否通过引用传递的类型,例如string[]的ElementType是string。
if (type.IsByRef) type = type.GetElementType();
// 例如:Nullable<Int32>,underlyingType = Int32
Type underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null)
{
return genNullableCaster(GetCaster(underlyingType));
}
ObjectCast oc;
// 尝试获取基础的转换函数,列表包含c#的基础类型和LuaTable和LuaFunction。
if (!castersMap.TryGetValue(type, out oc))
{
oc = genCaster(type);// 生成类型转换函数
castersMap.Add(type, oc);
}
return oc;
}
上述的castersMap记录转换方法列表和前面tryGetGetFuncByType相比,只多了LuaFunction和LuaTable的转换方法,至于为什么会有重复的转换方法,这里我也没想明白。
不过可以看出上面方法处理情况3、4中的一部分,剩余的部分就到了最后的genCaster方法,这个方法比较长,主要就是类型的分类处理了,贴出源码方便大家看。
private ObjectCast genCaster(Type type)
{
ObjectCast fixTypeGetter = (RealStatePtr L, int idx, object target) =>
{
if (LuaAPI.lua_type(L, idx) == LuaTypes.LUA_TUSERDATA)
{
object obj = translator.SafeGetCSObj(L, idx);
return (obj != null && type.IsAssignableFrom(obj.GetType())) ? obj : null;
}
return null;
};
// 能否分配给Delegate
if (typeof(Delegate).IsAssignableFrom(type))
{
return (RealStatePtr L, int idx, object target) =>
{
object obj = fixTypeGetter(L, idx, target);
if (obj != null) return obj;
if (!LuaAPI.lua_isfunction(L, idx))
{
return null;
}
return translator.CreateDelegateBridge(L, type, idx);
};
}
else if (typeof(DelegateBridgeBase).IsAssignableFrom(type))
{
return (RealStatePtr L, int idx, object target) =>
{
object obj = fixTypeGetter(L, idx, target);
if (obj != null) return obj;
if (!LuaAPI.lua_isfunction(L, idx))
{
return null;
}
return translator.CreateDelegateBridge(L, null, idx);
};
}
else if (type.IsInterface())
{
return (RealStatePtr L, int idx, object target) =>
{
object obj = fixTypeGetter(L, idx, target);
if (obj != null) return obj;
if (!LuaAPI.lua_istable(L, idx))
{
return null;
}
return translator.CreateInterfaceBridge(L, type, idx);
};
}
else if (type.IsEnum())
{
return (RealStatePtr L, int idx, object target) =>
{
object obj = fixTypeGetter(L, idx, target);
if (obj != null) return obj;
LuaTypes lua_type = LuaAPI.lua_type(L, idx);
if (lua_type == LuaTypes.LUA_TSTRING)
{
return Enum.Parse(type, LuaAPI.lua_tostring(L, idx));
}
else if (lua_type == LuaTypes.LUA_TNUMBER)
{
return Enum.ToObject(type, LuaAPI.xlua_tointeger(L, idx));
}
throw new InvalidCastException(&#34;invalid value for enum &#34; + type);
};
}
else if (type.IsArray)
{
return (RealStatePtr L, int idx, object target) =>
{
object obj = fixTypeGetter(L, idx, target);
if (obj != null) return obj;
if (!LuaAPI.lua_istable(L, idx))
{
return null;
}
uint len = LuaAPI.xlua_objlen(L, idx);
int n = LuaAPI.lua_gettop(L);
idx = idx > 0 ? idx : LuaAPI.lua_gettop(L) + idx + 1;// abs of index
Type et = type.GetElementType();
ObjectCast elementCaster = GetCaster(et);
Array ary = target == null ? Array.CreateInstance(et, (int)len) : target as Array;
if (!LuaAPI.lua_checkstack(L, 1))
{
throw new Exception(&#34;stack overflow while cast to Array&#34;);
}
for (int i = 0; i < len; ++i)
{
LuaAPI.lua_pushnumber(L, i + 1);
LuaAPI.lua_rawget(L, idx);
if (et.IsPrimitive())
{
if (!StaticLuaCallbacks.TryPrimitiveArraySet(type, L, ary, i, n + 1))
{
ary.SetValue(elementCaster(L, n + 1, null), i);
}
}
else
{
if (InternalGlobals.genTryArraySetPtr == null
|| !InternalGlobals.genTryArraySetPtr(type, L, translator, ary, i, n + 1))
{
ary.SetValue(elementCaster(L, n + 1, null), i);
}
}
LuaAPI.lua_pop(L, 1);
}
return ary;
};
}
else if (typeof(IList).IsAssignableFrom(type) && type.IsGenericType())
{
Type elementType = type.GetGenericArguments();
ObjectCast elementCaster = GetCaster(elementType);
return (RealStatePtr L, int idx, object target) =>
{
object obj = fixTypeGetter(L, idx, target);
if (obj != null) return obj;
if (!LuaAPI.lua_istable(L, idx))
{
return null;
}
obj = target == null ? Activator.CreateInstance(type) : target;
int n = LuaAPI.lua_gettop(L);
idx = idx > 0 ? idx : LuaAPI.lua_gettop(L) + idx + 1;// abs of index
IList list = obj as IList;
uint len = LuaAPI.xlua_objlen(L, idx);
if (!LuaAPI.lua_checkstack(L, 1))
{
throw new Exception(&#34;stack overflow while cast to IList&#34;);
}
for (int i = 0; i < len; ++i)
{
LuaAPI.lua_pushnumber(L, i + 1);
LuaAPI.lua_rawget(L, idx);
if (i < list.Count && target != null)
{
if (translator.Assignable(L, n + 1, elementType))
{
list = elementCaster(L, n + 1, list); ;
}
}
else
{
if (translator.Assignable(L, n + 1, elementType))
{
list.Add(elementCaster(L, n + 1, null));
}
}
LuaAPI.lua_pop(L, 1);
}
return obj;
};
}
else if (typeof(IDictionary).IsAssignableFrom(type) && type.IsGenericType())
{
Type keyType = type.GetGenericArguments();
ObjectCast keyCaster = GetCaster(keyType);
Type valueType = type.GetGenericArguments();
ObjectCast valueCaster = GetCaster(valueType);
return (RealStatePtr L, int idx, object target) =>
{
object obj = fixTypeGetter(L, idx, target);
if (obj != null) return obj;
if (!LuaAPI.lua_istable(L, idx))
{
return null;
}
IDictionary dic = (target == null ? Activator.CreateInstance(type) : target) as IDictionary;
int n = LuaAPI.lua_gettop(L);
idx = idx > 0 ? idx : LuaAPI.lua_gettop(L) + idx + 1;// abs of index
LuaAPI.lua_pushnil(L);
if (!LuaAPI.lua_checkstack(L, 1))
{
throw new Exception(&#34;stack overflow while cast to IDictionary&#34;);
}
while (LuaAPI.lua_next(L, idx) != 0)
{
if (translator.Assignable(L, n + 1, keyType) && translator.Assignable(L, n + 2, valueType))
{
object k = keyCaster(L, n + 1, null);
dic = valueCaster(L, n + 2, !dic.Contains(k) ? null : dic);
}
LuaAPI.lua_pop(L, 1); // removes value, keeps key for next iteration
}
return dic;
};
}
else if ((type.IsClass() && type.GetConstructor(System.Type.EmptyTypes) != null) || (type.IsValueType() && !type.IsEnum())) //class has default construtor
{
return (RealStatePtr L, int idx, object target) =>
{
object obj = fixTypeGetter(L, idx, target);
if (obj != null) return obj;
if (!LuaAPI.lua_istable(L, idx))
{
return null;
}
obj = target == null ? Activator.CreateInstance(type) : target;
int n = LuaAPI.lua_gettop(L);
idx = idx > 0 ? idx : LuaAPI.lua_gettop(L) + idx + 1;// abs of index
if (!LuaAPI.lua_checkstack(L, 1))
{
throw new Exception(&#34;stack overflow while cast to &#34; + type);
}
foreach (FieldInfo field in type.GetFields())
{
LuaAPI.xlua_pushasciistring(L, field.Name);
LuaAPI.lua_rawget(L, idx);
if (!LuaAPI.lua_isnil(L, -1))
{
try
{
field.SetValue(obj, GetCaster(field.FieldType)(L, n + 1,
target == null || field.FieldType.IsPrimitive() || field.FieldType == typeof(string) ? null : field.GetValue(obj)));
}
catch (Exception e)
{
throw new Exception(&#34;exception in tran &#34; + field.Name + &#34;, msg=&#34; + e.Message);
}
}
LuaAPI.lua_pop(L, 1);
}
return obj;
};
}
else
{
return fixTypeGetter;
}
}
这里以table转C#类为例,实际上就是枚举C#类属性,根据属性名取出table的值,根据类型进行转换并赋值。其他就不再赘述。
至此整个C#访问Lua流程就已经结束了。
总结
C#访问Lua,只能通过LuaTable的Get方法访问,关键技术点:
[*]实例化LuaTable时,会将栈顶的tabel加入到引用表的下一个空闲位置,并返回索引,LuaTable通过持有索引来与table产生映射。
[*]通过LuaTable.Get方法传入key取出值放入lua栈顶,根据这个值类型:
[*]如果类型本来就是C#实例,则直接通过UserData记录的C#侧缓存表的索引值取出实例。
[*]其他则获取转换函数并调用获取最终C#需要的值。
页:
[1]