什么是 wrap 文件
每个 wrap 文件都是对一个 c# 类的包装, 在 lua 中, 通过对 wrap 类中的函数调用, 间接的对 c# 实例进行操作.
wrap 类文件生成和使用的总体流程
生成一个 wrap 文件的流程
这部分主要通过分析类的反射信息完成.
wrap 文件内容解析
使用 UnityEngine_GameObjectWrap.cs 进行举例.
注册部分
- public static void Register(LuaState L)
- {
- L.BeginClass(typeof(UnityEngine.GameObject), typeof(UnityEngine.Object));
- L.RegFunction("CreatePrimitive", CreatePrimitive);
- L.RegFunction("GetComponent", GetComponent);
- L.RegFunction("GetComponentInChildren", GetComponentInChildren);
- L.RegFunction("GetComponentInParent", GetComponentInParent);
- L.RegFunction("GetComponents", GetComponents);
- L.RegFunction("GetComponentsInChildren", GetComponentsInChildren);
- L.RegFunction("GetComponentsInParent", GetComponentsInParent);
- L.RegFunction("SetActive", SetActive);
- L.RegFunction("CompareTag", CompareTag);
- L.RegFunction("FindGameObjectWithTag", FindGameObjectWithTag);
- L.RegFunction("FindWithTag", FindWithTag);
- L.RegFunction("FindGameObjectsWithTag", FindGameObjectsWithTag);
- L.RegFunction("Find", Find);
- L.RegFunction("AddComponent", AddComponent);
- L.RegFunction("BroadcastMessage", BroadcastMessage);
- L.RegFunction("SendMessageUpwards", SendMessageUpwards);
- L.RegFunction("SendMessage", SendMessage);
- L.RegFunction("New", _CreateUnityEngine_GameObject);
- L.RegFunction("__eq", op_Equality);
- L.RegFunction("__tostring", ToLua.op_ToString);
- L.RegVar("transform", get_transform, null);
- L.RegVar("layer", get_layer, set_layer);
- L.RegVar("activeSelf", get_activeSelf, null);
- L.RegVar("activeInHierarchy", get_activeInHierarchy, null);
- L.RegVar("isStatic", get_isStatic, set_isStatic);
- L.RegVar("tag", get_tag, set_tag);
- L.RegVar("scene", get_scene, null);
- L.RegVar("gameObject", get_gameObject, null);
- L.EndClass();
- }
这部分代码由 GenRegisterFunction()生成, 可以看到, 这些代码分为了 4 部分:
1.BeginClass 部分, 负责类在 lua 中的初始化部分
2.RegFunction 部分, 负责将函数注册到 lua 中
3.RegVar 部分, 负责将变量和属性注册到 lua 中
4.EndClass 部分, 负责类结束注册的收尾工作
BeginClass 部分
1用于创建类和类的元表, 如果类的元表的元表(类的元表是承载每个类方法和属性的实体, 类的元表的元表就是类的父类)
2将类添加到 loaded 表中.
3设置每个类的元表的通用的元方法和属性,__gc,name,ref,__cal,__index,__newindex.
RegFunction 部分
每一个 RefFunction 做的事都很简单, 将每个函数转化为一个指针, 然后添加到类的元表中去, 与将一个 c 函数注册到 lua 中是一样的.
RegVar 部分
每一个变量或属性或被包装成 get_xxx,set_xxx 函数注册添加到类的元表的 gettag,settag 表中去, 用于调用和获取.
EndClass 部分
做了两件事:
1设置类的元表
2把该类加到所在模块代表的表中(如将 GameObject 加入到 UnityEngine 表中)
每个函数的实体部分
由于构造函数, this[],get_xxx,set_xxx 的原理都差不多, 都是通过反射的信息生成的, 所以放在一起用一个实例讲一下(使用 GameObject 的 GetComponent 函数进行说明).
- [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
- static int GetComponent(IntPtr L)
- {
- try
- {
- // 获取栈中参数的个数
- int count = LuaDLL.lua_gettop(L);
- // 根据栈中元素的个数和元素的类型判断该使用那一个重载
- if (count == 2 && TypeChecker.CheckTypes<string>(L, 2))
- {
- // 将栈底的元素取出来, 这个 obj 在栈中是一个 fulluserdata, 需要先将这个 fulluserdata 转化成对应的 c# 实例, 也就是调用这个 GetComponent 函数的 GameObject 实例
- UnityEngine.GameObject obj = (UnityEngine.GameObject)ToLua.CheckObject(L, 1, typeof(UnityEngine.GameObject));
- // 将栈底的上一个元素取出来, 也就是 GetComponent(string type)的参数
- string arg0 = ToLua.ToString(L, 2);
- // 通过 obj,arg0 直接第调用 GetCompent(string type)函数
- UnityEngine.Component o = obj.GetComponent(arg0);
- // 将调用结果压栈
- ToLua.Push(L, o);
- // 返回参数的个数
- return 1;
- }
- // 另一个 GetComponent 的重载, 跟上一个差不多, 就不详细说明了
- else if (count == 2 && TypeChecker.CheckTypes<System.Type>(L, 2))
- {
- UnityEngine.GameObject obj = (UnityEngine.GameObject)ToLua.CheckObject(L, 1, typeof(UnityEngine.GameObject));
- System.Type arg0 = (System.Type)ToLua.ToObject(L, 2);
- UnityEngine.Component o = obj.GetComponent(arg0);
- ToLua.Push(L, o);
- return 1;
- }
- // 参数数量或类型不对, 没有找到对应的重载, 抛出错误
- else
- {
- return LuaDLL.luaL_throw(L, "invalid arguments to method: UnityEngine.GameObject.GetComponent");
- }
- }
- catch (Exception e)
- {
- return LuaDLL.toluaL_exception(L, e);
- }
- }
可以看到, GetComponent 函数的内容, 其实就是通过反射分析 GetComponent 的重载个数, 每个重载的参数个数, 类型生成的. 具体内容和 lua 调用 c 函数差不多.
每个函数实际的调用过程
假如说在 lua 中有这么一个调用:
- local tempGameObject = UnityEngine.GameObject("temp")
- local transform = tempGameObject.GetComponent("Transform")
第二行代码对应的实际调用过程是:
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 实例与这个元表的关系:
1GameObject 类: 其实只是一个放在_G 表中供人调用的一个充当索引的表, 我们通过它来触发 GameObject 元表的各种元方法, 实现对 c# 类的使用.
2GameObject 的实例: 是一个 fulluserdata, 内容为一个整数, 这个整数代表了这个实例在 objects 表中的索引 (objects 是一个用 list 实现的回收链表, lua 中调用的 c# 类实例都存在这个里面, 后面会讲这个 objects 表), 每次在 lua 中调用一个 c# 实例的方法时, 都会通过这个索引找到这个索引在 c# 中对应的实例, 然后进行操作, 最后将操作结果转化为一个 fulluserdata(或 lua 的内建类型, 如 bool 等) 压栈, 结束调用.
在 lua 中调用一个 c# 实例中的函数或变量的过程
- local tempGameObject = UnityEngine.GameObject("temp")
- local instanceID = tempGameObject.GetInstanceID()
在了解了 GameObject 元表后, 这些只是一些基础的元表操作, 就不多做解释.
lua 中 c# 实例的真正存储位置
前面说了每一个 c# 实例在 lua 中是一个内容为整数索引的 fulluserdata, 在进行函数调用时, 通过这个整数索引查找和调用这个索引代表的实例的函数和变量.
生成或使用一个代表 c# 实例的 lua 变量的过程大概是这样的.
还用这个例子来说明:
- local tempGameObject = UnityEngine.GameObject("temp")
- local transform = tempGameObject.GetComponent("Transform")
所以说 lua 中调用和创建的 c# 实例实际都是存在 c# 中的 objects 表中, lua 中的变量只是一个持有该 c# 实例索引位置的 fulluserdata, 并没有直接对 c# 实例进行引用.
对 c# 实例进行函数的调用和变量的修改都是通过元表调用操作 wrap 文件中的函数进行的.
以上就是 c# 类如何通过 wrap 类在 lua 中进行使用的原理.
来源: https://www.cnblogs.com/blueberryzzz/p/9672342.html