tolua#代码简要分析
简介
tolua#是Unity静态绑定lua的一个解决方案,它通过C#提供的反射信息分析代码并生成包装的类。它是一个用来简化在C#中集成lua的插件,可以自动生成用于在lua中访问Unity的绑定代码,并把C#中的常量、变量、函数、属性、类以及枚举暴露给lua。它是从cstolua衍变而来。从它的名字可以看出,它是集成了原来的tolua代码通过二次封装写了一个C#与tolua(c)的一个中间层。
All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections

基础
要想了解tolua#是如何集成的我们需要对C#的一些特性有一些了解,比如了解C#与原生代码交互的方式等。,我们假设读者已经对lua和tolua++有一个比较熟悉,我们略过lua与c或者C++的交互方式,主要介绍一些C#的特性,来帮助我们接下来分析tolua#的集成原理。
C#特性
Attribute
Attribute 是一种可由用户自由定义的修饰符(Modifier),可以用来修饰各种需要被修饰的目标。特性Attribute 的作用是添加元数据。元数据可以被工具支持,比如:编译器用元数据来辅助编译,调试器用元数据来调试程序。Unity以及tolua#中就会用Attribute来辅助做一些事情。
值类型与引用类型
只所以要提这两个概念,是因为很好得理解这两个概念有助于我们写出比较高效的C#代码。
我们知道,C#中的每一种类型要么是值类型,要么是引用类型。所以每个对象要么是值类型的实例,要么是引用类型的实例。
引用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即直接继承System.ValueType。
作为所有类型的基类,System.Object提供了一组方法,这些方法在所有类型中都能找到,其中包含toString方法及clone等方法。
System.ValueType直接继承System.Object,即System.ValueType本身是一个类类型,而不是值类型;System.ValueType没有添加任何成员,但覆盖了所继承的一些方法,使其更适合于值类型。例如,ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。

简单了解了值类型与引用类型那么我们下面来看下C#中的装箱和拆箱的概念。
装箱和拆箱
装箱和拆箱是值类型和引用类型之间相互转换是要执行的操作。
1. 装箱在值类型向引用类型转换时发生
2. 拆箱在引用类型向值类型转换时发生
object objValue = ; int value = (int)objValue;
上面的两行代码会执行一次装箱操作将整形数字常量4装箱成引用类型object变量objValue;然后又执行一次拆箱操作,将存储到堆上的引用变量objValue存储到局部整形值类型变量value中。
同样我们需要看下IL代码:
.locals init ( [] object objValue, [] int32 'value' ) //上面IL声明两个局部变量object类型的objValue和int32类型的value变量 IL_0000: nop IL_0001: ldc.i4. //将整型数字4压入栈 IL_0002: box [mscorlib]System.Int32 //执行IL box指令,在内存堆中申请System.Int32类型需要的堆空间 IL_0007: stloc. //弹出堆栈上的变量,将它存储到索引为0的局部变量中 IL_0008: ldloc.//将索引为0的局部变量(即objValue变量)压入栈 IL_0009: unbox.any [mscorlib]System.Int32 //执行IL 拆箱指令unbox.any 将引用类型object转换成System.Int32类型 IL_000e: stloc. //将栈上的数据存储到索引为1的局部变量即value
拆箱操作的执行过程和装箱操作过程正好相反,是将存储在堆上的引用类型值转换为值类型并给值类型变量。
C#调用原生代码
因为tolua#底层库是使用的tolua(c语言编写),那么就需要通过C#来调用原生代码,我们从LuaDll.cs中摘取一段代码来演示如何从C#中调用原生代码。
Public class LuaDll
{
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern void lua_close(IntPtr luaState);
}
其中LUADLL对应的字符串就是tolua,在不同的平台上mono会去加载对应的tolua.dll或者tolua.so等文件并调用对应的函数。具体可以参考mono官方的教程。
tolua#集成
tolua#集成主要分为两部分,一部分是运行时需要的代码包括一些手写的和自动生成的绑定代码,另一部分是编辑器相关代码,主要提供代码生成、编译lua文件等操作,具体就是Unity编辑器中提供的功能。用mono打开整个tolua#的工程,文件结构大体如下所示:

Runtime
Source
Generate 这个文件里面是生成的绑定代码
LuaConst.cs 这个文件是一些lua路径等配置文件。
ToLua
BaseLua 一些基础类型的绑定代码
Core 提供的一些核心功能,包括封装的LuaFunction LuaTable LuaThread LuaState LuaEvent、调用tolua原生代码等等。
Examples 示例
Misc 杂项,目前有LuaClient LuaCoroutine(协程) LuaLooper(用于tick) LuaResLoader(用于加载lua文件)
Reflection 反射相关
Editor
Editor
Custom
CustomSettings.cs 自定义配置文件,用于定义哪些类作为静态类型、哪些类需要导出、哪些附加委托需要导出等。
ToLua
Editor
Extend 扩展一些类的方法。
ToLuaExport.cs 真正生成lua绑定的代码
ToLuaMenu.cs Lua菜单上功能对应的代码
ToLuaTree.cs 辅助树结构
Generate All 流程
了解了tolua#的大致文件结构,下面我们来看下tolua#的Generate All 这个功能来分析下它的生成过程。生成绑定代码主要放在ToLuaExport.cs里面,我们并不会对每一个函数进行细致的讲解,如果有什么不了解的地方,可以直接看它的代码。

GenLuaDelegates函数
生成委托绑定的代码,它会从CustomSettings.customDelegateList里面取出所有自定义导出的委托列表,然后把CustomSettings.customTypeList里面的所有类型中的委托根据一定规则加入到list中,最后调用ToLuaExport.GenDelegates()方法来生成委托绑定的代码,生成的代码在DelegateFactory.cs文件中。
由于该函数比较简单,我们这里不做展开,可以直接查看ToLuaExport.cs中的GenDelegates()并配合DelegateFactory.cs来查看。
GenerateClassWraps 函数
遍历所有需要导出的类,然后调用ToLuaExport.Generate()方法来生成类的绑定代码。
下面我们来看下ToLuaExport.Generate()方法,其基本流程如下所示:


从上面的流程图我们可以看到,整个过程还是比较清楚的,如果这个类是枚举类型,那么它会调用枚举导出的接口,而如果这个类型是一个普通的类,那么它就会调用上图所示的相应的流程将代码导出。至于结构体类型,目前应该是只支持一些特定的结构体,需要在lua中对应一份实现(Assets\ToLua\Lua目录中),当然它生成的代码也有一些依赖于tolua#的核心运行时,我们前面简单的讲解了如何在编辑器中生成绑定代码,接下来我们讲一下它的核心运行时。
tolua#的核心运行时
tolua#的运行代码包含SourceàGenerate下面的绑定代码以及ToLuaàBaseType代码以及Core下面的核心代码。接下来我们着重讲一下Core下面的几个主要类。
LuaAttribute.cs
我们前面基础知识部分已经讲过,它在tolua#生成绑定代码时做一些标示使用。
LuaBaseRef.cs
Lua中对象对应C#中对象的一个基类,主要作用是有一个reference指向lua里面的对象,引用计数判断两个对象是否相等等。

比如LuaFunction里面的reference是指向lua里面的一个闭包的,而LuaTable的reference是指向lua中的一个table的。
LuaDll.cs
这个类的主要作用就是实现了C#调用原生代码的功能,其中的原理我们也在上面的基础章节提到了如何在C#中调用原生代码,这里我们就不展开去讲了。
LuaState.cs
这里面是对真正的lua_State的封装,包括初始化lua路径,加载相应的lua文件,注册我们前面生成的绑定代码以及各种辅助函数。
ObjectTranslator.cs
接下来,我们着重说一下这个ObjectTranslator这个类,这个类代码不多,它存在的主要意义就是给lua中对C#对象的交互提供了基础,简单来说就是C#中的对象在传给lua时并不是直接把对象暴露给了lua,而是在这个OjbectTranslator里面注册并返回一个索引(可以理解为windows编程中的句柄),并把这个索引包装成一个userdata传递给lua,并且设置元表。具体可以查看tolua_pushnewudata代码,如下所示:
// tolua# 代码
static void PushUserData(IntPtr L, object o, int reference)
{
int index;
ObjectTranslator translator = ObjectTranslator.Get(L);
if (translator.Getudata(o, out index))
{
if (LuaDLL.tolua_pushudata(L, index))
{
return;
}
translator.Destroyudata(index);
}
index = translator.AddObject(o);
LuaDLL.tolua_pushnewudata(L, reference, index);
}
// tolua++ 代码
LUALIB_API void tolua_pushnewudata(lua_State *L, int metaRef, int index)
{
lua_getref(L, LUA_RIDX_UBOX);
tolua_newudata(L, index);
lua_getref(L, metaRef);
lua_setmetatable(L, -);
lua_pushvalue(L, -);
lua_rawseti(L, -, index);
lua_remove(L, -);
}
而在lua需要通过上面传到lua里面的对象调用C#的方法时,它会调用ToLua.CheckObject或者ToLua.ToObject从ObjectTranslator获取真正的C#对象。下面我们把ToLua.ToObject的代码做个示例:
public static object ToObject(IntPtr L, int stackPos)
{
int udata = LuaDLL.tolua_rawnetobj(L, stackPos);
if (udata != -)
{
ObjectTranslator translator = ObjectTranslator.Get(L);
return translator.GetObject(udata);
}
return null;
}
总结
通过对tolua#的简单分析,我们对tolua#是怎么实现lua与Unity交互有了一个基础的认识,如果想对tolua#有一个比较深入的了解,那么就需要我们仔细去研究代码、示例以及用它来实际地去做些东西。
因为看的时间不是很多,所以理解上难免有错误,如果发现问题还请指正。前段时间也完整实现了一套虚幻4中的使用lua框架,希望有时间的话也能跟大家分享一下,当然如果你有兴趣了解,也可以留言,这样我会尽量抽时间来把实现的具体细节跟大家分享一下。
tolua#代码简要分析的更多相关文章
- Deeplab v3+的结构代码简要分析
添加了解码模块来重构精确的图像物体边界.对比如图 deeplab v3+采用了与deeplab v3类似的多尺度带洞卷积结构ASPP,然后通过上采样,以及与不同卷积层相拼接,最终经过卷积以及上采样得到 ...
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析
原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析 系列目录 最新比较闲,为了学习下Android的开发构建ASP.NET ...
- CVE-2015-5122 简要分析(2016.4)
CVE-2015-5122 简要分析 背景 最近在学习Flash漏洞的分析,其与IE漏洞的分析还是有诸多的不同(不便)之处,折腾了一阵子终于克服了没有符号表.Flash的超时定时器等问题.所以找到了去 ...
- Android初级教程通过简要分析“土司”源码,来自实现定义土司理论探讨
由于系统自带的土司瞬间即逝,而且非常难看.因此我们就希望自定义自己的土司风格.有些实例就是基于自定义土司完成的,例如金山卫士的火箭发射,基本原理就是个土司.但是在做出自己的土司风格之前,还是要简要分析 ...
- Android Hal层简要分析
Android Hal层简要分析 Android Hal层(即 Hardware Abstraction Layer)是Google开发的Android系统里上层应用对底层硬件操作屏蔽的一个软件层次, ...
- RxJava && Agera 从源码简要分析基本调用流程(2)
版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...
- MapReduce启动的Map/Reduce子任务简要分析
对于Hadoop来说,是通过在DataNode中启动Map/Reduce java进程的方式来实现分布式计算处理的,那么就从源码层简要分析一下hadoop中启动Map/Reduce任务的过程. ...
- CVPR2018 关于视频目标跟踪(Object Tracking)的论文简要分析与总结
本文转自:https://blog.csdn.net/weixin_40645129/article/details/81173088 CVPR2018已公布关于视频目标跟踪的论文简要分析与总结 一, ...
- 转:InnoDB多版本(MVCC)实现简要分析
InnoDB多版本(MVCC)实现简要分析 基本知识 假设对于多版本(MVCC)的基础知识,有所了解.InnoDB为了实现多版本的一致读,采用的是基于回滚段的协议. 行结构 InnoDB表数据的组织方 ...
随机推荐
- ADO.net参数化查询陷阱
避免SQL漏洞注入攻击,往往采用的是参数化查询!然而在使用参数化查询中,往往为了方便就直接通过构造方法来进行数据的初始化了,然而这样就引发一个这样的问题,当参数值为0时,就出现参数为空的情况了. 一. ...
- web微信开发前期准备最新详细流程
一.申请配置测试公众号与配置本地服务器 1.打开浏览器,输入:http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login,微信扫码确 ...
- Unity Shader 知识点总结(一)
在学习了一段时间的Unity Shader后,打算写一些知识总结,便于今后的查找.如有错误,希望大家指出更改. 本文参照的unity入门精要一书,做一个知识归纳,如有兴趣可以看看其开源的部分,是一本比 ...
- Java日志工具之SLF4J
SLF4J全称为Simple Logging Facade for Java (简单日志门面),作为各种日志框架的简单门面或者抽象,包括 java.util.logging, log4j, logba ...
- AndroidStudio运行项目出现Unsupported method: AndroidProject.getPluginGeneration()错误解决办法
一.错误描述 今天在使用AndroidStudio运行项目时出现了一个Unsupported method: AndroidProject.getPluginGeneration()错误,如下图所示: ...
- 使用Task的一些知识优化了一下同事的多线程协作取消的一串代码
最近在看一个同事的代码,代码的本意是在main方法中开启10个线程,用这10个线程来处理一批业务逻辑,在某一时刻当你命令console退出的时候,这个 时候不是立即让console退出,而是需要等待1 ...
- H5 表单元素
HTML5 表单元素 HTML5 的新的表单元素: HTML5 拥有若干涉及表单的元素和属性. 本章介绍以下新的表单元素: datalist keygen output 浏览器支持 Input typ ...
- Selenium自动化脚本开发总结
Selenium Selenium 是ThoughtWorks专门为Web应用程序编写的一个验收测试工具. Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE.Mo ...
- 分享小知识:善用Group By排序
以下列举了公用表/临时表/聚合函数三个因素为例子(覆盖索引因素除外,有利用此类索引都会以索引顺序) 环境: Microsoft SQL Server 2014 (SP1-GDR) (KB319472 ...
- web CSS的知识- 关于后代选择器,子选择器,兄弟选择器的使用
1. 后代选择器官方解释:后代选择器可以选择作为某元素后代的元素.理解:选择某一标签的后代中,所有的此标签标记例:ul em {color:red;}就是选择,h1标签后代中中,所有的em.代码如下: ...