tolua之wrap文件的原理与使用
本文内容转载自:https://www.cnblogs.com/blueberryzzz/p/9672342.html 。非常感谢原作者慷慨地授权转载,比心!@blueberryzzz 是位大神,欢迎大家关注他的博客。马三对原文的排版与结构做了微调,以便更合适阅读。
一、什么是wrap文件
每个wrap文件都是对一个c#类的包装,在lua中,通过对wrap类中的函数调用,间接的对c#实例进行操作。
二、wrap类文件生成和使用的总体流程
三、生成一个wrap文件的流程
这部分主要通过分析类的反射信息完成。
四、wrap文件内容解析
使用UnityEngine_GameObjectWrap.cs进行举例。
1.注册部分
1 public static void Register(LuaState L) 2 { 3 L.BeginClass(typeof(UnityEngine.GameObject), typeof(UnityEngine.Object)); 4 L.RegFunction("CreatePrimitive", CreatePrimitive); 5 L.RegFunction("GetComponent", GetComponent); 6 L.RegFunction("GetComponentInChildren", GetComponentInChildren); 7 L.RegFunction("GetComponentInParent", GetComponentInParent); 8 L.RegFunction("GetComponents", GetComponents); 9 L.RegFunction("GetComponentsInChildren", GetComponentsInChildren); 10 L.RegFunction("GetComponentsInParent", GetComponentsInParent); 11 L.RegFunction("SetActive", SetActive); 12 L.RegFunction("CompareTag", CompareTag); 13 L.RegFunction("FindGameObjectWithTag", FindGameObjectWithTag); 14 L.RegFunction("FindWithTag", FindWithTag); 15 L.RegFunction("FindGameObjectsWithTag", FindGameObjectsWithTag); 16 L.RegFunction("Find", Find); 17 L.RegFunction("AddComponent", AddComponent); 18 L.RegFunction("BroadcastMessage", BroadcastMessage); 19 L.RegFunction("SendMessageUpwards", SendMessageUpwards); 20 L.RegFunction("SendMessage", SendMessage); 21 L.RegFunction("New", _CreateUnityEngine_GameObject); 22 L.RegFunction("__eq", op_Equality); 23 L.RegFunction("__tostring", ToLua.op_ToString); 24 L.RegVar("transform", get_transform, null); 25 L.RegVar("layer", get_layer, set_layer); 26 L.RegVar("activeSelf", get_activeSelf, null); 27 L.RegVar("activeInHierarchy", get_activeInHierarchy, null); 28 L.RegVar("isStatic", get_isStatic, set_isStatic); 29 L.RegVar("tag", get_tag, set_tag); 30 L.RegVar("scene", get_scene, null); 31 L.RegVar("gameObject", get_gameObject, null); 32 L.EndClass(); 33 }
这部分代码由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表中)
2.每个函数的实体部分
由于构造函数,this[],get_xxx,set_xxx的原理都差不多,都是通过反射的信息生成的,所以放在一起用一个实例讲一下(使用GameObject的GetComponent函数进行说明)。
1 [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] 2 static int GetComponent(IntPtr L) 3 { 4 try 5 { 6 //获取栈中参数的个数 7 int count = LuaDLL.lua_gettop(L); 8 //根据栈中元素的个数和元素的类型判断该使用那一个重载 9 if (count == 2 && TypeChecker.CheckTypes<string>(L, 2)) 10 { 11 //将栈底的元素取出来,这个obj在栈中是一个fulluserdata,需要先将这个fulluserdata转化成对应的c#实例,也就是调用这个GetComponent函数的GameObject实例 12 UnityEngine.GameObject obj = (UnityEngine.GameObject)ToLua.CheckObject(L, 1, typeof(UnityEngine.GameObject)); 13 //将栈底的上一个元素取出来,也就是GetComponent(string type)的参数 14 string arg0 = ToLua.ToString(L, 2); 15 //通过obj,arg0直接第调用GetCompent(string type)函数 16 UnityEngine.Component o = obj.GetComponent(arg0); 17 //将调用结果压栈 18 ToLua.Push(L, o); 19 //返回参数的个数 20 return 1; 21 } 22 //另一个GetComponent的重载,跟上一个差不多,就不详细说明了 23 else if (count == 2 && TypeChecker.CheckTypes<System.Type>(L, 2)) 24 { 25 UnityEngine.GameObject obj = (UnityEngine.GameObject)ToLua.CheckObject(L, 1, typeof(UnityEngine.GameObject)); 26 System.Type arg0 = (System.Type)ToLua.ToObject(L, 2); 27 UnityEngine.Component o = obj.GetComponent(arg0); 28 ToLua.Push(L, o); 29 return 1; 30 } 31 //参数数量或类型不对,没有找到对应的重载,抛出错误 32 else 33 { 34 return LuaDLL.luaL_throw(L, "invalid arguments to method: UnityEngine.GameObject.GetComponent"); 35 } 36 } 37 catch (Exception e) 38 { 39 return LuaDLL.toluaL_exception(L, e); 40 } 41 }
可以看到,GetComponent函数的内容,其实就是通过反射分析GetComponent的重载个数,每个重载的参数个数,类型生成的。具体内容和lua调用c函数差不多。
3.每个函数实际的调用过程
假如说在lua中有这么一个调用:
1 local tempGameObject = UnityEngine.GameObject("temp") 2 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实例与这个元表的关系:
- GameObject类:其实只是一个放在_G表中供人调用的一个充当索引的表,我们通过它来触发GameObject元表的各种元方法,实现对c#类的使用。
- GameObject的实例:是一个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/yptianma/p/11797916.html
- [基础]电话/手机常见验证的Javascript示例
- 按照Web Service方式调用WCF服务的问题
- 利用Lucene打造站内搜索引擎的思路
- Lucene.Net 删除索引DeleteDocuments的注意事项
- 使用VS2010的Database 项目模板统一管理数据库对象
- 利用c#制作托盘程序,并禁止多个应用实例运行
- dotNET跨平台相关文档整理
- .NET Core 2.0 正式发布信息汇总
- iis7.0上发布mvc4.0网站
- 遍历文件夹所有文件(示例)
- Visual Studio 2017 : client version 1.22 is too old
- httphandler和httpmodule的区别
- 每周.NET前沿技术文章摘要(2017-06-21)
- Access数据库多表连接查询
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 使用bootstrap时下拉菜单失效问题解决
- 使用文件字节输出流FileOutputStream写入文件
- jdk1.8 自带的Base64加密与解密
- 缓冲字节输入流BufferedInputStream
- 在页面上写一个验证码
- 缓冲字节输出流BufferedOutputStream
- EL表达式获取简单数据与复杂数据(调用类)+EL表达式获取Map集合与list集合数据
- 用缓冲字节流,复制一个照片
- 09小结:封装结果集或实体类时,有多个对象的解决方法
- 序列化与反序列化Serializable,Externalizable
- response.getWriter().write()和 response.getWriter().print()的区别:
- java使用TCP,由客户端向服务端传输图片,(电脑与电脑)或(同一台电脑)
- 使用druid
- UDP实现多人聊天室
- 请求(doFilter)与响应乱码(BaseController+自定义注解@ContentType(““))集中处理