tolua之wrap文件的原理与使用
每个wrap文件都是对一个c#类的包装,在lua中,通过对wrap类中的函数调用,间接的对c#实例进行操作。
wrap类文件生成和使用的总体流程
生成一个wrap文件的流程
这部分主要通过分析类的反射信息完成。
wrap文件内容解析
使用UnityEngine_GameObjectWrap.cs进行举例。
注册部分
1 | public static void (LuaState L) |
这部分代码由GenRegisterFunction()生成,可以看到,这些代码分为了4部分:
1.BeginClass部分,负责类在lua中的初始化部分
2.RegFunction部分,负责将函数注册到lua中
3.RegVar部分,负责将变量和属性注册到lua中
4.EndClass部分,负责类结束注册的收尾工作
BeginClass部分
①用于创建类和类的元表,如果类的元表的元表(类的元表是承载每个类方法和属性的实体,类的元表的元表就是类的父类)
②将类添加到loaded表中。
③设置每个类的元表的通用的元方法和属性,gc,name,ref,cal,index,newindex。
RegFunction部分
每一个RefFunction做的事都很简单,将每个函数转化为一个指针,然后添加到类的元表中去,与将一个c函数注册到lua中是一样的。
RegVar部分
每一个变量或属性或被包装成get_xxx,set_xxx函数注册添加到类的元表的gettag,settag表中去,用于调用和获取。
EndClass部分
做了两件事:
①设置类的元表
②把该类加到所在模块代表的表中(如将GameObject加入到UnityEngine表中)
每个函数的实体部分
由于构造函数,this[],get_xxx,set_xxx的原理都差不多,都是通过反射的信息生成的,所以放在一起用一个实例讲一下(使用GameObject的GetComponent函数进行说明)。
1 | [] |
可以看到,GetComponent函数的内容,其实就是通过反射分析GetComponent的重载个数,每个重载的参数个数,类型生成的。具体内容和lua调用c函数差不多。
每个函数实际的调用过程
假如说在lua中有这么一个调用:
1 | local tempGameObject = UnityEngine.GameObject("temp") |
第二行代码对应的实际调用过程是:
1.先去tempGameObject的元表GameObject元表中尝试去取GetComponent函数,取到了。
2.调用取到的GetComponent函数,调用时会将tempGameObject,”Transform”作为参数先压栈,然后调用GetComponent函数。
3.接下来就进入GetComponent函数内部进行操作,因为生成了新的ci,所以此时栈中只有tempGameOjbect,”Transfrom”两个元素。
4.根据参数的数量和类型判断需要使用的重载。
5.通过tempGameObject代表的c#实例的索引,在objects表中找到对应的实例。同时取出”Transform”这个参数,准备进行真正的函数调用。
6.执行obj.GetComponent(arg0),将结果包装成一个fulluserdata后压栈,结束调用。
7.lua中的transfrom变量赋值为这个压栈的fulluserdata。
8.结束。
其中3-7的操作都在c#中进行,也就是wrap文件中的GetComponent函数。
一个类通过wrap文件注册进lua虚拟机后是什么样子的
使用GameObjectWrap进行举例
可以看到GameObject的所有功能都是通过一个元表实现的,通过这个元表可以调用GameObjectWrap文件中的各个函数来实现对GameObject实例的操作,这个元表对使用者来说是不可见的,因为我们平时只会在代码中调用GameObject类,GameObject实例,并不会直接引用到这个元表,接下来来分析一下GameObject类,GameObject实例与这个元表的关系:
①GameObject类:其实只是一个放在_G表中供人调用的一个充当索引的表,我们通过它来触发GameObject元表的各种元方法,实现对c#类的使用。
②GameObject的实例:是一个fulluserdata,内容为一个整数,这个整数代表了这个实例在objects表中的索引(objects是一个用list实现的回收链表,lua中调用的c#类实例都存在这个里面,后面会讲这个objects表),每次在lua中调用一个c#实例的方法时,都会通过这个索引找到这个索引在c#中对应的实例,然后进行操作,最后将操作结果转化为一个fulluserdata(或lua的内建类型,如bool等)压栈,结束调用。
在lua中调用一个c#实例中的函数或变量的过程
1 | local tempGameObject = UnityEngine.GameObject("temp") |
在了解了GameObject元表后,这些只是一些基础的元表操作,就不多做解释。
lua中c#实例的真正存储位置
前面说了每一个c#实例在lua中是一个内容为整数索引的fulluserdata,在进行函数调用时,通过这个整数索引查找和调用这个索引代表的实例的函数和变量。
生成或使用一个代表c#实例的lua变量的过程大概是这样的。
还用这个例子来说明:
1 | local tempGameObject = UnityEngine.GameObject("temp") |
所以说lua中调用和创建的c#实例实际都是存在c#中的objects表中,lua中的变量只是一个持有该c#实例索引位置的fulluserdata,并没有直接对c#实例进行引用。
对c#实例进行函数的调用和变量的修改都是通过元表调用操作wrap文件中的函数进行的。
以上就是c#类如何通过wrap类在lua中进行使用的原理。