oopl001 发表于 2023-8-16 08:12

tolua源码分析(九)反射

上一节我们讨论了如安在lua层扩展担任C#类的机制。本节我们来看一下tolua的反射机制,它主要用于在lua层访谒没有事先wrap过的C#类对象和方式。主要实现的思想就是操作C#自带的反射机制,将反射相关的函数注册到lua层。此次的例子是example 22,同样我们主要不雅察看例子中的lua代码:
require 'tolua.reflection'         
tolua.loadassembly('Assembly-CSharp')      
local BindingFlags = require 'System.Reflection.BindingFlags'

function DoClick()
    print('do click')      
end

function Test()
    local t = typeof('TestExport')      
    local func = tolua.getmethod(t, 'TestReflection')         
    func:Call()      
    func:Destroy()
    func = nil

    local objs = {Vector3.one, Vector3.zero}
    local array = tolua.toarray(objs, typeof(Vector3))
    local obj = tolua.createinstance(t, array)

    local constructor = tolua.getconstructor(t, typeof(Vector3):MakeArrayType())
    local obj2 = constructor:Call(array)      
    constructor:Destroy()

    func = tolua.getmethod(t, 'Test', typeof('System.Int32'):MakeByRefType())      
    local r, o = func:Call(obj, 123)
    print(r..':'..o)
    func:Destroy()

    local property = tolua.getproperty(t, 'Number')
    local num = property:Get(obj, null)
    print('object Number: '..num)
    property:Set(obj, 456, null)
    num = property:Get(obj, null)
    property:Destroy()
    print('object Number: '..num)

    local field = tolua.getfield(t, 'field')
    num = field:Get(obj)
    print('object field: '.. num)
    field:Set(obj, 2048)
    num = field:Get(obj)
    field:Destroy()
    print('object field: '.. num)      

    field = tolua.getfield(t, 'OnClick')
    local onClick = field:Get(obj)      
    onClick = onClick + DoClick      
    field:Set(obj, onClick)      
    local click = field:Get(obj)
    click:DynamicInvoke()
    field:Destroy()
    click:Destroy()
end这个例子可以分为好几个部门,拆开来看,首先是反射调用一个无参的C#静态函数TestReflection,它的实现如下:
public sealed class TestExport
{
    public static void TestReflection()
    {
      Debugger.Log(”call TestReflection()”);      
    }
}
相关的lua代码如下:
local t = typeof('TestExport')      
local func = tolua.getmethod(t, 'TestReflection')         
func:Call()      
func:Destroy()
func = nil全局的typeof定义在typeof.lua中,如果传入的参数是string,会先在type缓存中查找,查找不到则会调用到C#层的FindType函数,它会从C#的Assembly中查找到对应的Type,然后作为userdata压入到lua层,也就是第1行t的值是一个userdata。
第2行的tolua.getmethod会调用到C#层的GetMethod函数,该函数将userdata转换为对应的Type,然后操作反射获取到对应的MethodInfo。为了把反射相关的信息一股脑儿地告诉了lua层,这里引入了一个名为LuaMethod的类,它代表一个C#的反射函数,包含class,method,以及参数类型列表信息:
public sealed class LuaMethod
{      
    MethodInfo method = null;
    List<Type> list = new List<Type>();
    Type kclass = null;

   
    public LuaMethod(MethodInfo md, Type t, Type[] types)
    {
      method = md;
      kclass = t;            

      if (types != null)
      {
            list.AddRange(types);
      }
    }
}
所以,这里第2行得到的func是一个代表LuaMethod对象的userdata。那么第3行实际上调用到的就是LuaMethod类的Call方式,它其实就是对C#反射调用方式的过程做了一层封装。由于这里反射的函数是静态无参而且没有返回值的,因此lua层并不需要提供额外的参数给C#,也不需要从C#层获取返回值信息。
然后来看一下lua层如何反射调用C#的构造函数,这里给出了两种方式,首先看一下第一种:
local objs = {Vector3.one, Vector3.zero}
local array = tolua.toarray(objs, typeof(Vector3))
local obj = tolua.createinstance(t, array)它实际调用到的是C#层参数为Vector3数组的构造函数:
public sealed class TestExport
{
    public TestExport(Vector3[] v)
    {
      Debugger.Log(”call TestExport(params Vector3[] v)”);
    }
}
这里lua代码第2行也调用了全局的typeof,而这里传入的不是string,Vector3我们之前提到过是lua层本身实现的一个table,对于table类型来说,如果type缓存中不存在,则调用的是C#的GetClassType:
static int GetClassType(IntPtr L)
{
    int reference = LuaDLL.tolua_getmetatableref(L, 1);

    if (reference > 0)
    {
      Type t = LuaStatic.GetClassType(L, reference);
      Push(L, t);
    }
    else
    {
      int ret = LuaDLL.tolua_getvaluetype(L, -1);

      if (ret != LuaValueType.None)
      {
            Type t = TypeChecker.LuaValueTypeMap;
            Push(L, t);
      }
      else
      {
            Debugger.LogError(”type not register to lua”);
            LuaDLL.lua_pushnil(L);
      }
    }

    return 1;
}
当然,由于这里的Vector3是lua本身实现的类型,所以C#层压根找不到对应的meta reference,因此还是要回到lua层通过GetLuaValueType查找,再通过LuaValueTypeMap映射到C#的Type。tolua.toarray所做的事情就是将lua层的table转换成C#的Array,对应的C#函数为TableToArray。那么,第2行执行结束得到的array是一个暗示C#层Array的userdata。类似地,第3行的tolua.createinstance就是对C#的Activator.CreateInstance进行了一层封装,将转换后的Type类型和构造函数的参数传递进去。
再看下第二种:
local constructor = tolua.getconstructor(t, typeof(Vector3):MakeArrayType())
local obj2 = constructor:Call(array)由上文可知,typeof(Vector3)返回的是C#的Type类型,代表C#的Vector3,那么调用MakeArrayType得到的就是Vector3[]的Type类型。tolua.getconstructor实际调用的是C#层的GetConstructor:
static int GetConstructor(IntPtr L)
{
    try
    {
      int count = LuaDLL.lua_gettop(L);
      Type t = (Type)ToLua.CheckObject(L, 1, typeof(Type));               
      Type[] types = null;

      if (count > 1)
      {
            types = new Type;

            for (int i = 2; i <= count; i++)
            {
                Type ti = ToLua.CheckMonoType(L, i);
                if (ti == null) LuaDLL.luaL_typerror(L, i, ”Type”);
                types = ti;
            }
      }

      ConstructorInfo ret = t.GetConstructor(types);
      PushLuaConstructor(L, ret, types);
    }
    catch (Exception e)
    {
      return LuaDLL.toluaL_exception(L, e);
    }

    return 1;
}
这个函数实际上也是对Type.GetConstructor进行了封装。得到ConstructorInfo之后,为了把它和构造函数的参数列表类型信息关联起来,这里引入了一个LuaConstructor的辅助类来辅佐做这件事情:
public sealed class LuaConstructor
{
    ConstructorInfo method = null;
    List<Type> list = null;

   
    public LuaConstructor(ConstructorInfo func, Type[] types)
    {
      method = func;            

      if (types != null)
      {
            list = new List<Type>(types);
      }
    }
}
lua层得到的constructor就是代表这个LuaConstructor类型的userdata。类似地,它的Call方式也是对ConstructorInfo.Invoke进行了封装,同时会对lua层传入的数据进行类型查抄。
例子中的下一个部门是关于ref/out类型的:
func = tolua.getmethod(t, &#39;Test&#39;, typeof(&#39;System.Int32&#39;):MakeByRefType())      
local r, o = func:Call(obj, 123)
func:Destroy()这个和前面类似,这里就不展开了,它反射的对应C#方式为:
public int Test(out int i)
{
    i = 1024;
    Debugger.Log(”call Test(ref int i)”);
    return 3;
}
下一部门是通过反射获取property的:
local property = tolua.getproperty(t, &#39;Number&#39;)
local num = property:Get(obj, null)
property:Set(obj, 456, null)
num = property:Get(obj, null)
property:Destroy()按照之前的分析,这里我们也很容易猜出getproperty返回的是一个LuaProperty辅助类,它包含了PropertyInfo和Type信息,Type主要是为了在lua层传入参数时进行类型查抄:
public sealed class LuaProperty
{
    PropertyInfo property = null;
    Type kclass = null;      

   
    public LuaProperty(PropertyInfo prop, Type t)
    {
      property = prop;
      kclass = t;            
    }
}
它反射的对应C#属性为:
public int Number
{
    get
    {            
      return number;
    }

    set
    {            
      number = value;            
    }
}
例子的最后一部门,是field相关的:
local field = tolua.getfield(t, &#39;field&#39;)
num = field:Get(obj)
print(&#39;object field: &#39;.. num)
field:Set(obj, 2048)
num = field:Get(obj)
field:Destroy()
print(&#39;object field: &#39;.. num)      

field = tolua.getfield(t, &#39;OnClick&#39;)
local onClick = field:Get(obj)      
onClick = onClick + DoClick      
field:Set(obj, onClick)      
local click = field:Get(obj)
click:DynamicInvoke()
field:Destroy()
click:Destroy()同样地,这里也有一个LuaField的辅助类,帮我们保留着FieldInfo和Type信息:
public sealed class LuaField
{
    FieldInfo field = null;
    Type kclass = null;

   
    public LuaField(FieldInfo info, Type t)
    {
      field = info;
      kclass = t;            
    }
}
这里反射的对应C#成员为:
public int field = 1024;
public System.Action OnClick = delegate { };

下一节我们将探讨,如安在tolua中传递布局体,以及新增一个struct类型,需要做哪些工作。

如果你感觉我的文章有辅佐,欢迎存眷我的微信公众号我是真的想做游戏啊

散步人生 发表于 2023-8-16 08:13

加油,第十章什么时候更新

dzbear 发表于 2023-8-16 08:14

感谢支持[害羞] 最近有点忙,可能下周更新
页: [1]
查看完整版本: tolua源码分析(九)反射