找回密码
 立即注册
查看: 259|回复: 2

tolua源码分析(九)反射

[复制链接]
发表于 2023-8-16 08:12 | 显示全部楼层 |阅读模式
上一节我们讨论了如安在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;

    [NoToLuaAttribute]
    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[ret];
            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[count - 1];

            for (int i = 2; i <= count; i++)
            {
                Type ti = ToLua.CheckMonoType(L, i);
                if (ti == null) LuaDLL.luaL_typerror(L, i, ”Type”);
                types[i - 2] = 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;

    [NoToLuaAttribute]
    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;        

    [NoToLuaAttribute]
    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;

    [NoToLuaAttribute]
    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 | 显示全部楼层
加油,第十章什么时候更新
发表于 2023-8-16 08:14 | 显示全部楼层
感谢支持[害羞] 最近有点忙,可能下周更新
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-22 21:20 , Processed in 0.102803 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表