调用无返回值函数(lua访问image的SetNativeSize)
调用返回C#对象的函数(lua访问image的mainTexture)
通过Require\Dofile调用lua以及通过DoString执行DoString
最近在看lua泄漏的问题,接着就暴露出自己的一些问题,对于lua的认识更多的是停留在语法使用上,而对于lua如何产生泄漏,如何检查,如何优化,一直都没有一个清晰的认识,甚至对于lua与其他语言的交互也是没有什么认识。问题的本质是没有系统的了解过lua,也没有一定的知识储备。那本着一劳永逸的犯懒精神,集中时间和精力,去了解这门语言,之后再把自己的理解写成博客,似乎是一个性价比超高的选择。
Lua和C语言通信的主要方法是一个无所不在的虚拟栈。几乎所有的API的调用都会操作这个栈上的值。所有的数据交换,无论是Lua到C语言或C语言到Lua都通过这个栈来完成。此外,还可以用这个栈来保存一些中间结果。站可以解决Lua和C语言之间的两大差异,第一种差异是Lua使用垃圾收集,而C语言要求显示地释放内存;第二种是Lua使用动态类型,而C语言使用静态类型,同时,虚拟栈可以很好的处理静态类型与动态类型的相互转换。
如下图,unity C#先是生成tolua C#(warp文件),然后通过这些自动生成的tolua C#和tolua C交互,tolua C再借助lua栈与lua交互。

图二 C#与lua调用简图
其主要逻辑就是两点,一个是lua内部通过持有translatorID,来确定是哪个C#对象,另一个是通过调用实例table所设置的metatable的接口来确定是哪个C#接口。通过warp.cs接口向lua注册相关逻辑,注册后的结构如下图所示。

当调用发生时,实例对象将其所持有的translatorId通过其metatable调用,通过lua栈,最终调用C#端注册的warp文件。
以下图中的对Image进行注册的warp文件为例,当lua端调用实例img(table)的SetNativeSize接口时,按照[方法索引,参数,参数数量]的顺序将数据压进lua栈(栈底是方法索引,栈顶是参数数量),此时的参数就是实例img(table)中的translatorId,当调用到C# Warp中的SetNativeSize方法时,会先检查参数数量(CheckArgsCount),接着通过进栈的translatorId,去ObjectTranslator中查找对应的image对象。

再来看一看带返回值的情况,以下图中的对Image进行注册的warp文件为例,当lua端访问实例img(table)的mainTexture属性时,按照[方法索引,参数,参数数量]的顺序将数据压进lua栈(栈底是方法索引,栈顶是参数数量),此时的参数就是实例img(table)中的translatorId,当调用到C# Warp中的访问属性的方法时,先获得Image实例,然后获得mainTexture属性,然后进行压栈处理。
通过下图的调用顺序和代码可以很清楚的看到,压栈的操作顺序:先获取lua内存中的类型索引reference,然后将mainTexture对象加进ObjectTranslator中获取对应索引index。最后,调用tolua_pushnewudata接口,reference作为lua实例的原表的索引key,index用作lua实例的translatorId。

接下来,以常见的写法gameobj.transform.position = pos进行分析,看lua层写下这一行代码,发生了什么。因为短短一行代码,却发生了非常非常多的事情,为了更直观一点,我们把这行代码调用过的关键luaApi以及ToLua相关的关键步骤列出来(以ToLua+cstolua导出为准,gameobj是GameObject类型,pos是Vector3):
第一步:.transform.position
UnityEngine_GameObjectWrap.get_transform lua想从gameobj拿到transform,对应gameobj.transform
LuaDLL.luanet_rawnetobj 把lua中的gameobj变成c#可以辨认的id
ObjectTranslator.TryGetValue 用这个id,从ObjectTranslator中获取c#的gameobject对象
gameobject.transform 准备这么多,这里终于真正执行c#获取gameobject.transform了
ObjectTranslator.AddObject 给transform分配一个id,这个id会在lua中用来代表这个transform,
transform要保存到ObjectTranslator供未来查找
LuaDLL.luanet_newudata 在lua分配一个userdata,把id存进去,用来表示即将返回给lua的transform
LuaDLL.lua_setmetatable 给这个userdata附上metatable,让你可以transform.position这样使用它
LuaDLL.lua_pushvalue 返回transform,后面做些收尾
LuaDLL.lua_rawseti
LuaDLL.lua_remove
第二步:= pos
TransformWrap.set_position lua想把pos设置到transform.position
LuaDLL.luanet_rawnetobj 把lua中的transform变成c#可以辨认的id
ObjectTranslator.TryGetValue 用这个id,从ObjectTranslator中获取c#的transform对象
LuaDLL.tolua_getfloat3 从lua中拿到Vector3的3个float值返回给c#
lua_getfield + lua_tonumber 3次 拿xyz的值,退栈
lua_pop
transform.position = new Vector3(x,y,z) 准备了这么多,终于执行transform.position = pos赋值了
C#调用lua基本上就是3种:通过Require、DoFile;通过DoString执行DoString;通过lua虚拟机对象获取对应的对象实例,对对象实例进行操作。

简单描述一下获取LuaFunction实例的步骤流程,核心逻辑是先将”testFunction”转换到fullPath,然后调用lua_getglobal接口来获取对象,接着就是通过toluaL_ref来获取reference索引,reference和luaState构成了LuaFunction对象的逻辑。
LuaFunction的Call逻辑主要是三步:BeginPCall、Pcall、EndPCall。先通过tolua_beginpcall传递refrence索引,然后通过lua_pcall传递参数和起始栈的id,这样就可以在lua层执行lua的方法,最后再根据起始栈id恢复当前lua栈到执行前的层级。

先明确在lua中的泄漏是什么,这种GC语言的泄漏大都是其引用关系没有正常释放,存在一个或者多个地方持有但不使用对象,这种情况下的对象不能被正常释放,且这种对象的数量不断的上升,我理解的泄漏是这种情景。
根据上述章节的内容去寻找会发生泄漏的场景,一般是2个:
重复性执行的逻辑中需要一直创建table,而这些新创建的table又在存储table中作为索引key,当这个创建的table不再使用时,存储table还持有新建的table,泄漏就发生了。
这种场景下的泄漏处理的方法其实也很简单,将存储table设置为弱引用table,这样lua在GC的时候就不会判定存储table还在持有已经不用的新建table。
具体细节可以参考这篇博文:Step By Step(Lua弱引用table)
在做业务逻辑开发的时候,大都会有这种场景:C#需要持有lua对象。比如tolua的框架下会定制一些UI工具类,通过设置luaFunction来完成对lua的事件回调。
在组件的使用周期完毕之后如果不对luaFunction进行释放,是有可能引起内存泄漏的。比如不断的注册lua的匿名回调函数,注册的函数会让C#持有refrence,但因为一直没有释放所以也就不会执行luaDll.toluaL_unref接口,那么lua就认为该lua函数对象是被使用的,又因为匿名函数在执行的时候,每注册一次都会生成一个新的函数对象,然后随着注册次数的增加,函数对象的数量也就一直增加,泄漏就这样发生了。
因此,要是想要规避这种情景下的泄漏,从两个地方着手:
1.C#工具使用周期结束执行luaFunction or luaTable的Dispose接口。
2.注册函数尽可能少的使用匿名函数
严格的来说,这个并不算泄漏,因为最后那些临时对象会被GC掉,放到这里是因为,不加处理的数学运算,且调用频繁的话,会大大增加内存的增长率,使得GC操作变得相对频繁起来,所以,在某种程度上,它已经算是一种泄漏了。具体细节和优化,可以参考下面这篇文章
好Lua+Unity,让性能飞起来——Lua与C#交互篇_我动了谁的奶酪-CSDN博客_unity 纯lua
了解lua所采用的GC原理,对处理lua内存泄漏的问题,以及检查有很大的帮助。
Lua采用的是Mark-sweep算法:每次GC的时候,对所有对象进行一次扫描,如果该对象不存在引用,则被回收,反之则保存。在Lua5.0及其更早的版本中,Lua的GC是一次性不可被打断的过程,使用的Mark算法是双色标记算法(Two color mark),这样系统中对象的非黑即白,要么被引用,要么不被引用,这会带来一个问题:在GC的过程中如果新加入对象,这时候新加入的对象无论怎么设置都会带来问题,如果设置为白色,则如果处于回收阶段,则该对象会在没有遍历其关联对象的情况下被回收;如果标记为黑色,那么没有被扫描就被标记为不可回收,是不正确的。在Lua5.1后,Lua都采用分布回收以及三色增量标记清除算法(Tri-color incremental mark and sweep)
其基本的原理伪代码,参考书中原文为:
每个新创建的对象颜色设置为白色
//初始化阶段
遍历root节点中引用的对象,从白色置为灰色,并且放入到灰色节点列表中
//标记阶段
while(灰色链表中还有未扫描的元素):
从中取出一个对象,将其置为黑色
遍历这个对象关联的其他所有对象:
if 为白色
标记为灰色,加入到灰色链表中(insert to the head)
//回收阶段
遍历所有对象:
if 为白色,
没有被引用的对象,执行回收
else
重新塞入到对象链表中,等待下一轮GC
这是一些关于一些GC的文章:
Lua与C#的交互主要是依托于lua栈,在相互调用的过程中,双方各自对到对方一个id,一个能索引到对应对象的id,然后对方根据这个id通过lua栈向对方传递操作行为,有真实对象所在的领域去执行具体的操作。大致流程如下图:

lua程序设计(第二版)[巴西]莱鲁 [2008-1]
Unity项目常见Lua解决方案性能比较_UWA—简单优化,优化简单!-CSDN博客
Lua内存泄漏应对方法_xocoder's coding life-CSDN博客_lua内存泄漏
如何编译各平台使用的库-以编译tolua为例_linxinfa的专栏-CSDN博客
【Unity游戏开发】tolua之wrap文件的原理与使用 - 马三小伙儿 - 博客园
用好Lua+Unity,让性能飞起来——Lua与C#交互篇_我动了谁的奶酪-CSDN博客_unity 纯lua
【Lua知识整理】——Lua栈_suhuaiqiang_janlay的专栏-CSDN博客_lua 栈
……
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我好像记得Lua有类似Ruby的method_missing的东西。还是我记错了? 最佳答案 表的metatable的__index和__newindex可以用于与Ruby的method_missing相同的效果。 关于ruby-难道Lua没有和Ruby的method_missing相媲美的东西吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/7732154/
这可能是个愚蠢的问题。但是,我是一个新手......你怎么能在交互式rubyshell中有多行代码?好像你只能有一条长线。按回车键运行代码。无论如何我可以在不运行代码的情况下跳到下一行吗?再次抱歉,如果这是一个愚蠢的问题。谢谢。 最佳答案 这是一个例子:2.1.2:053>a=1=>12.1.2:054>b=2=>22.1.2:055>a+b=>32.1.2:056>ifa>b#Thecode‘if..."startsthedefinitionoftheconditionalstatement.2.1.2:057?>puts"f
如何在ruby中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL
我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手