简介

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#代码简要分析的更多相关文章

  1. Deeplab v3+的结构代码简要分析

    添加了解码模块来重构精确的图像物体边界.对比如图 deeplab v3+采用了与deeplab v3类似的多尺度带洞卷积结构ASPP,然后通过上采样,以及与不同卷积层相拼接,最终经过卷积以及上采样得到 ...

  2. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析 系列目录 最新比较闲,为了学习下Android的开发构建ASP.NET ...

  3. CVE-2015-5122 简要分析(2016.4)

    CVE-2015-5122 简要分析 背景 最近在学习Flash漏洞的分析,其与IE漏洞的分析还是有诸多的不同(不便)之处,折腾了一阵子终于克服了没有符号表.Flash的超时定时器等问题.所以找到了去 ...

  4. Android初级教程通过简要分析“土司”源码,来自实现定义土司理论探讨

    由于系统自带的土司瞬间即逝,而且非常难看.因此我们就希望自定义自己的土司风格.有些实例就是基于自定义土司完成的,例如金山卫士的火箭发射,基本原理就是个土司.但是在做出自己的土司风格之前,还是要简要分析 ...

  5. Android Hal层简要分析

    Android Hal层简要分析 Android Hal层(即 Hardware Abstraction Layer)是Google开发的Android系统里上层应用对底层硬件操作屏蔽的一个软件层次, ...

  6. RxJava && Agera 从源码简要分析基本调用流程(2)

    版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...

  7. MapReduce启动的Map/Reduce子任务简要分析

      对于Hadoop来说,是通过在DataNode中启动Map/Reduce java进程的方式来实现分布式计算处理的,那么就从源码层简要分析一下hadoop中启动Map/Reduce任务的过程.   ...

  8. CVPR2018 关于视频目标跟踪(Object Tracking)的论文简要分析与总结

    本文转自:https://blog.csdn.net/weixin_40645129/article/details/81173088 CVPR2018已公布关于视频目标跟踪的论文简要分析与总结 一, ...

  9. 转:InnoDB多版本(MVCC)实现简要分析

    InnoDB多版本(MVCC)实现简要分析 基本知识 假设对于多版本(MVCC)的基础知识,有所了解.InnoDB为了实现多版本的一致读,采用的是基于回滚段的协议. 行结构 InnoDB表数据的组织方 ...

随机推荐

  1. 对spring,struts,hibernate及MVC的理解

    对于spring,hibernate,struts等框架,刚开始的时候总是会很迷茫,不知道他们是用来做什么的. 1.对框架的作用理解 个人认为框架的作用是把代码进行了分类,减少了代码的耦合性. 如果不 ...

  2. Linux的常用基本命令。

    Linux的常用基本命令. 首先启动Linux.启动完毕后需要进行用户的登录,选择登陆的用户不同自然权限也不一样,其中"系统管理员"拥有最高权限. 在启动Linux后屏幕出现如下界 ...

  3. css3滚动效果

    .css{ -webkit-transition-duration: .3s;    transition-duration: .3s; }

  4. Spark_总结五

    Spark_总结五 1.Storm 和 SparkStreaming区别 Storm                      纯实时的流式处理,来一条数据就立即进行处理 SparkStreaming ...

  5. 1339 / 1163: [Baltic2008]Mafia

    1163: [Baltic2008]Mafia Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 96  Solved: 60[Submit][Statu ...

  6. 1653: [Usaco2006 Feb]Backward Digit Sums

    1653: [Usaco2006 Feb]Backward Digit Sums Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 285  Solved:  ...

  7. JS实现购物车特效

    学习通过JavaScript实现类似于淘宝的购物车效果,包括商品的单选.全选.删除.修改数量.价格计算.数目计算.预览等功能. 1. 实现兼容低版本IE的getElementsByClassName( ...

  8. sublime text笔记

    sublime text确实是一个很不错的编辑器,而且还是跨平台的,这个很不错.---Linux V2.0.2 有些时候可以比VIM要好有些 1.安装: apt-get install sublime ...

  9. js闭包深度讲解

    js的闭包是学习js过程中的重点,但是不得不说也是一个难点呀,其涉及到了js中的很多概念.我在学习js中也遇到了很多问题,这篇文章算是一个对闭包的总结,文章主要内容为闭包的基本知识点与对其理解上的一些 ...

  10. 使用slice和concat对数组的深拷贝和浅拷贝

    一.数组浅拷贝 在使用JavaScript对数组进行操作的时候,我们经常需要将数组进行备份. 如下代码,如果只是简单才用赋值的方法,那么我们只要更改其中的任何一个,然后其他的也会跟着改变,这就导致了问 ...