说是IronLua,但是现在继承IronLua名号的F#版和后来重写的C#版(https://github.com/ericmj/IronLua.git)都已经久不更新,此外采用DLR的真IronLua——Nua也只有测试版。除此之外这个替代品还有Eluant、UniLua(国产)等一堆,但都不够完善。
目前还活跃着的.NET平台的Lua Binding大体上有两个选择:Tao.Lua和NLua(LuaInterface) 。
Tao.Lua是Tao框架(现在更名OpenTK,但旧版的Tao仍可下载)的Lua组件,它是对Lua的直接绑定。NLua是LuaInterface停更之后接替它的工程,也可以算是标准的IronLua(官方解释中有一句:LuaInterface is now pure CIL).
网上有一篇文章对比了两者的速度,从结果来看是直接绑定的Tao.Lua胜出。(http://www.moddb.com/games/boxycraft/news/luainterface-vs-taolua)
首先来看看Tao.Lua:
写个简单的测试脚本test.lua:
-
function add(x)
return x+100
end
my = "Ulysses"
print(my)
hello = "15"
my_function(55,99)
return hello + 5
-
接下来是使用Tao.Lua的C#程序:
class Program { private static int my_function(IntPtr L) { int num = Lua.lua_gettop(L); Console.WriteLine("-- my_function() called with " + num + " arguments:"); for (int i = 1; i <= num; i++) { Console.WriteLine(string.Concat(new object[] { "-- argument ", i, ": ", Lua.lua_tostring(L, i) })); } Lua.lua_pushnumber(L, 123.0); return 1; } private static void report_errors(IntPtr L, int status) { if (status != 0) { Console.WriteLine("-- " + Lua.lua_tostring(L, -1)); Lua.lua_pop(L, 1); } } static void Main(string[] args) { string text = "test.lua"; int ans = 0; IntPtr l = Lua.luaL_newstate(); Lua.luaL_openlibs(l); //这两步是初始化 Lua.lua_register(l, "my_function", new Lua.lua_CFunction(my_function)); //绑定C#方法 Console.WriteLine("-- Loading file: " + text); int num = Lua.luaL_loadfile(l, text); //读取Lua文件,但是必须运行一次pcall才能真正读取 if (num == 0) { //num = Lua.lua_pcall(l, 0, -1, 0); num = Lua.lua_pcall(l, 0, 1, 0); //0个输入,1个输出 ans = (int)Lua.lua_tonumber(l, -1); //lua中没有int型,数字统一为double,第二个参数代表栈顶元素 Lua.lua_pop(l, 1); //把返回值清掉 Lua.lua_getglobal(l, "add"); //取得Lua脚本中的方法,getglobal要在call之后 Lua.lua_pushnumber(l, 200.0); //压栈传参 Lua.lua_pcall(l, 1, 1, 0); double ansd = Lua.lua_tonumber(l, -1); Console.WriteLine(ansd); } Console.WriteLine(ans); report_errors(l, num); Lua.lua_close(l); Console.ReadLine(); } }
这个测试中有以下几点:
1.向Lua中注册C#函数my_function(),之后在Lua脚本中直接调用成功。
2.在C#中调用Lua函数add(),然后压栈传参数,调用后取得结果。这个步骤必须在执行过pcall,也就是运行过脚本之后才能使用,仅是Load是不行的。
3.传值:
C#->Lua:使用getglobal也可以取得lua中的值,而setglobal可以改变值。
Lua->C#: 调用C#中的方法传值和改变值。使用C#中的lua_tostring/tonumber等。Lua中调用不能以table为参数,这点尚待研究。但是由于是直接绑定,传入C#中的是指针,使用Lua.lua_gettop(L)可以取得传入的参数个数,然后弹栈可以取得各个参数(如果是table会认为是1个参数),这基本上是可以接受任意参数的意思。
虽然有DoFile和DoString方法,但是使用时经常出错,正确的方法是先Load再使用pcall执行。(call执行时也会出错,这大概就是Do方法出错的原因所在)
从上可见Tao框架中的Lua继承了Tao的一贯风格:原味移植,保持C++时代的要素基本不变。无论是与openGL交互,还是与Lua交互,你翻出C++的例子看懂,基本上就能移植到C#上来。缺点是少了.NET特色,绑定没有简化调用的步骤。
下面再说NLua:
这是针对NLua的测试Lua脚本,只有一点改动:
-
function add(x)
return x+100
end
my = "Ulysses"
print(my)
hello = "15"
mtable = {55,99}
my_function(mtable)
return hello + 5
-
下面是NLua版的C#程序:
class Program { public static void my_function(LuaTable lt) { foreach (object arg in lt.Values) { Console.WriteLine(arg.ToString()); } Console.WriteLine("myFunction in C# called!"); } static void Main(string[] args) { Console.WriteLine("===下面是NLua测试==="); Program pro = new Program(); var luna = new NLua.Lua(); //已经把Lua虚拟机做成一个对象 luna.LoadCLRPackage(); //相当于NewState和OpenLib两个函数 luna.LoadFile("test.lua"); luna.RegisterFunction("my_function", pro, pro.GetType().GetMethod("my_function")); //绑定C#方法 object[] ans = luna.DoFile("test.lua"); //直接取得Lua脚本的运行结果,而且支持返回多个值 Console.WriteLine(ans[0].ToString()); Console.WriteLine(luna["hello"]); //直接读取相应变量的值 LuaFunction add = luna.GetFunction("add"); //取得Lua脚本中的方法,也是对象 object[] ansd = add.Call(200); //调用Lua方法,参数直接写进去,支持任意个参数 Console.WriteLine(ansd[0].ToString()); //取得结果 Console.ReadKey(); } }
经过上述程序,已经无需过多说明,简单几步就完成了:
1.向Lua中注册C#函数【RegisterFunction】
2.向C#中注册Lua函数【GetFunction】
3.取值改值——直接跟泛型没啥区别
4. 由于不弹栈,不能像Tao.Lua一样向C#中输入任意个参数,但是相对应地可以输入一个Table,因此还是任意参数,Table在C#中变成了泛型结构,对于这个数据类型NLua做的很好。(如果还想弹栈,可以试试LuaLib,我觉得这样已经够用了)
完全面向对象,高度集成,操作方便代码易读,这样的库谁不喜欢呢!如果不是过分追求效率,NLua真真是个极好的选择。
更棒的是Nlua作为Iron语言对.NET的调用。这是Iron语言的一贯优势,官网有一示例图。
此外,NLua还集成了一个KeraLua,这是LuaJIT(C编译器版Lua)的C# Binding.这一点也在NLua的TODO中,恐怕目前是采用这种方式暂时替代吧。KeraLua的思路与Tao.Lua十分类似,NLua的内部也需要调用KeraLua的一些结构,比如LuaState。KeraLua单独拿出来使的时候发现它没有dofile/dostring.只能通过pcall来调用。而NLua里面也有一个LuaLib,提供了直接与Lua和栈打交道的一堆方法和参数,与KeraLua基本重复。也许两者实际上是一个调用另一个的关系,也许是NLua的TODO正在实现中。不管怎样,大同小异,可按需使用。
下面附上KeraLua和LuaLib的例子:
public static void TestKLua() //采用LuaJIT以提升速度的解决方案 { IntPtr l = KeraLua.Lua.LuaLNewState(); KeraLua.Lua.LuaLOpenLibs(l); KeraLua.Lua.LuaNetLoadFile(l, "test.lua"); KeraLua.Lua.LuaNetPCall(l, 0, 0, 0); //必须先调用一次pcall读取Lua文件 KeraLua.Lua.LuaNetGetGlobal(l, "add"); KeraLua.Lua.LuaPushNumber(l, 200); KeraLua.Lua.LuaNetPCall(l, 1, 1, 0); double ans = KeraLua.Lua.LuaNetToNumber(l, -1); Console.WriteLine(ans); }
public static void TestLuaLib() //在NLua中直接操作Lua的解决方案 { KeraLua.LuaState l = LuaLib.LuaLNewState(); //用KeraLua里的LuaState替代了指针,还是离不开KeraLua啊 LuaLib.LuaLOpenLibs(l); LuaLib.LuaLLoadFile(l, "test.lua"); LuaLib.LuaPCall(l, 0, 0, 0); LuaLib.LuaGetGlobal(l, "add"); LuaLib.LuaPushNumber(l, 200.0); LuaLib.LuaPCall(l, 1, 1, 0); double ans = LuaLib.LuaToNumber(l, -1); Console.WriteLine(ans); }
此外关于在VS中使用Lua的工具,有一个国人(推测是游戏行业的)做的BabeLua非常棒。感兴趣的可以到codeplex上去找。