一直不明白LuaInterface和lua之间反射调用的原理,花了两天时间读了一下代码,稍微总结了一下
附上所用LuaInterface的地址,可以用git直接clone
https://github.com/fengxiaorui/luainterface
下面进入正题:
先说说两个关键的载入函数,也是进行反射调用的基础:
1、LoadAssembly
在Lua调用load_assembly后会被调用,目的是载入某个程序集到程序集缓存中
通过代码可以发现这里只是通过栈上传入的字符串加载对应的程序集,并加入到缓存中
(注意)这个程序集缓存在开启时就已经Add了正在执行的程序集,不需要重复加载
2、ImportType
在Lua调用import_type时被调用,从已经加载的程序集中查找对应的类,没有找到返回空,找到则调用pushType,往
堆栈上压一种新的类型,并缓存在luaNet_objects元表中,matable的类型为luaNet_class。这一步的主要作用是方便C#将这些类型压入堆栈后将返回值压进栈
然后会调用到把CLR Object压进栈的一般方法
这里会优先查找objectsBackMap这个索引表中查找这个object是否已经缓存,如果已经缓存返回对应的index,然后直接在名为luaNet_objects的元表中取第index项,即为要进栈的object
如果当前的索引表中没有找到这个对象,则说明需要插入一个新的对象,则调用pushNewObject
这里metatable为luaNet_metatable时说明要构建相应类型的元表,名称为AssemblyQualifiedName,这个元表会包含一个名为cache的table字段,用来缓存方法,和一个已经在全局域的luaNet_indexfunction作为其__index原方法
由于现在传入的metatable字段是luaNet_class,所以是直接取名为luaNet_class的元表(这个元表在ObjectTranslator初始化时已经生成好了,过程在createClassMetatable中)
然后将luaNet_class元表赋给新建的userdata,并将这个userdata设置为luaNet_objects元表的第index项,并留一份拷贝在栈上,至此,大工告成,类型成功import。当下次要压入同样类型到栈上时,只需要查找索引表找到index,然后去luaNet_objects元表的第index项直接取就好了。
然后是lua调用相应方法和返回值的过程:
1、CLR Object调用方法的流程是先调用自己的__index元方法,对应luaNet_indexfuction方法,定义如下:
这里会先取得以自己的元表(o.GetType().AssemblyQualifiedName为名的元表),查看相应的CSFuntion是否已经缓存在cache字段中,如果有就直接从cache字段里取,如果没有则调用get_object_member,利用反射的结构去查找对应的函数,并将结果和函数体返回到这里,缓存后将函数体返回
其中get_object_member方法值得一提:
它在C#对应的方法为getMethod:
这里会从堆栈取出对象和函数名,然后开始查找过程:
大部分情况下会走到这里:
可以注意到这里有一个Hashtable类型的memberCache,其中key为objType,value为另一个子HashTable,这个子HashTable存储了方法名和对应的LuaCSFuction。memberCache主要是在每次getMethod后缓存根据类型和方法名查找到的方法(LuaCSFuction),下一次在查找相同的方法时,直接从这个缓存表返回,省去了每次都要反射查找的问题
(C#这里有memberCache在缓存,lua那边每一个类的元表上还有Cache字段在缓存,这个相互关系待明确)
目前考虑memberCache这边函数和成员都有缓存,Cache这边只缓存函数
这里Getmember如果成功则将LuaCSFunction和结果入栈,回到刚才的luaNet_indexfuction方法处理
这里的LuaCSFunction实际上是一个函数代理,执行它就相当执行LuaMethodWrapper的call方法,这是一次反射调用,也就是说LuaInterface所有的C#函数调用实际上都是反射调用
LuaMethodWrapper这个类的作用就是根据提供的类型和方法名,反射调用相应的方法,并将结果压栈给lua
如果返回类型是一个基本类型,就调用相应的Lua API入栈:
如果返回类型是一个CLR类型,则会在push时调用:
至此就将一个封装好的object(userdata,同时设置了cache域和绑定__index元方法)压栈,至此就完成了全部的调用过程。
如果有不准确的地方,欢迎大家在下面评论留言讨论!