7.5 CLR/Lua
Limax提供一个clrlua项目,粘合C#与Lua,支持C#与Lua代码之间的互操作,用于实现C#脚本模式框架下的LuaScriptHandle。这一章介绍clrlua提供的功能。
-
7.5.1 编程接口
-
limax.script.Lua
创建该对象也就创建了一个lua虚拟机,使用该对象即可与lua虚拟机交互。
构造函数:
-
Lua(limax.script.Lua.ErrorHandle eh)
ErrorHandle 定义为委托 delegate void ErrorHandle(string msg); 用以接收lua错误信息。
成员函数:
-
object eval(string code)
执行code表示的代码chunk,返回结果。
-
object eval(string codepattern, params object[] parameters)
用变长参数列表填充codepattern中的占位符<0>,<1>......<n>,然后执行chunk,返回结果。占位符通过与之对应创建的上下文变量进行关联,在模板代码中应该当作变量名使用,而不能用引号括起来,否则将替换结果将是一个上下文变量名串。
返回的结果可能是任何类型,特别的可能返回LuaObject,LuaTable, LuaFunction三个类型。
-
Lua name(string chunkname)
为后续eval命名,如果运行时错误发生在被命名的eval的代码块中,错误信息中会前缀该名字,便于调试。该名字内部使用UTF8编码,建议不要使用非ASCII字符。
-
-
limax.script.LuaObject
无法转换为C#类型的Lua对象,哪些无法转换在类型映射一节介绍,该对象可以被C#持有,该对象上不提供任何操作,多数情况下需要传回lua虚拟机使用。
-
limax.script.LuaTable
该对象派生自LuaObject,实现了System.Collections.IDictionary接口, 关联到lua虚拟机中的table上。访问该对象, 也就访问了虚拟机中对应的table, 该对象上执行修改操作将体现到虚拟机中对应的table上, 反之亦然。
注意,通过limax.script.Lua创建的对象,在之后的文字中命名为Lua操作对象,区别于LuaObject。
-
limax.script.LuaFunction
一个变长参数委托,包装了一个派生自LuaObject,关联到lua虚拟机中的function上的对象。在该委托上进行调用,相当于调用了虚拟机中对应的function。
-
7.5.2 C#与Lua的互操作举例
首先定义Lua lua = new Lua((string msg)=>Console.WriteLine(msg));
-
C#操纵Lua
C#操纵Lua,通过eval方法调用实现。
-
hello world
lua.eval("print('hello world')"); lua.eval("print(<0>)", "hello world"); Console.WriteLine(lua.eval("return 'hello world'")); Console.WriteLine(lua.eval("return <0>", "hello world"));
输出:
hello world
hello world
hello world
hello world
其中, 第一行代码无需解释,第二行代码说明了占位符的使用, 第三行代码说明了eval可以返回lua虚拟机中持有的值, 第四行代码说明了一个值可以在C#, Lua间反复传递。
-
好好学习,天天向上
lua.eval("print('好好学习,天天向上')"); lua.eval("print(<0>)", "好好学习,天天向上"); Console.WriteLine(lua.eval("return '好好学习,天天向上'")); Console.WriteLine(lua.eval("return <0>", "好好学习,天天向上"));
输出:
濂藉ソ瀛︿範锛屽ぉ澶╁悜涓
濂藉ソ瀛︿範锛屽ぉ澶╁悜涓
好好学习,天天向上
好好学习,天天向上
这个例子说明了, C#中的Unicode字符串传递给Lua的时候使用UTF8编码, 从Lua传递回来使用UTF8解码。所以对于Unicode字符, Lua虚拟机看来是乱码, 回到C#将变得正确。
-
执行代码片断返回多值
object[] a = (object[])lua.eval("return 1,2,3"); foreach (object o in a) Console.WriteLine(o);
输出:
1
2
3
这个例子说明了,如果Lua代码返回多值,这些值被放置在object[]中返回。
-
执行代码片段返回table
IDictionary dict = (IDictionary)lua.eval("t = { a = 'A', b ='B' }\n return t "); foreach (DictionaryEntry e in dict) Console.WriteLine(e.Key + ":" + e.Value);
输出:
b:B
a:A
继续执行:
dict.Remove("a"); dict.Add("c", "C"); lua.eval("print (t.a)"); lua.eval("print (t.c)");
输出:
nil
C
这里可以看见,C#中的修改影响到了lua内部的table
接续执行:
lua.eval("t.d = 100"); foreach (DictionaryEntry e in dict) Console.WriteLine(e.Key + ":" + e.Value);
输出:
d:100
c:C
b:B
这里可以看出,lua内部对table的修改,也反映到C#一边。
-
执行代码片断返回function
LuaFunction func = (LuaFunction)lua.eval("return function(a,b) return a + b; end"); Console.WriteLine(func (10, 20));
输出:
30
这里可以看出,返回一个lua函数在C#中使用非常容易。
-
-
Lua操纵C#
首先定义一个C#实验类。
public class TestLua { public TestLua() { Console.WriteLine("Construct TestLua"); acc = add; } public TestLua(int x) { Console.WriteLine("Construct TestLua with " + x); acc = add; } public int add(int a, int b) { return a + b; } public string mystr; public int Prop { get; set; } public int this[int x] { get { return x + 1; } set { Console.WriteLine("Indexed Property set key = " + x + " value = " + value); } } public delegate int ACC(int a, int b); public ACC acc; }
这里应该看到,需要通过Lua访问的对象构造函数,方法,字段,属性,委托,必须声明为public。
-
构造对象
lua.eval("t0 = <0>()", typeof(TestLua)); lua.eval("t1 = <0>(100)", typeof(TestLua)); Console.WriteLine(lua.eval("return t0") is TestLua);
输出:
Construct TestLua
Construct TestLua with 100
True
在这里,类TestLua被传递进Lua虚拟机,在类上直接进行方法调用,也就创建了对象,通过参数个数的控制可以选择不同的方法进行调用。第三行把Lua中创建的变量t0传回C#进行验证,果然是TestLua对象。
-
方法调用
lua.eval("print (t0.add(1,2))");
输出:
3
如果是类的静态方法,同样可以调用。
-
字段访问
lua.eval("print(t0.mystr)"); lua.eval("t0.mystr='hello world'"); Console.WriteLine(lua.eval("return t0.mystr")); Console.WriteLine(((TestLua)lua.eval("return t0")).mystr);
输出
nil
helloworld
helloworld
第一行代码,输出mystr中的内容,对象构造时没有初始化该字段C#中为null,对应了Lua中的nil,第二行设置mystr的字段内容,所以第三第四行访问均正确返回了设置内容。
-
普通属性访问
lua.eval("print(t0.Prop)"); lua.eval("t0.Prop = t0.Prop + 10"); Console.WriteLine(lua.eval("return t0.Prop"));
输出:
0
10
第一行代码输出Prop的默认初始化值0,第二行相当于读取该属性,再设置该属性,属性上加上10,所以第三行输出10。
-
索引化属性访问
lua.eval("print(t0[100])"); lua.eval("t0[200] = 300");
输出:
101
Indexed Property set key = 200 value = 300
这里可以看到索引化属性的两个方法均被正确调用。
-
委托访问
lua.eval("print(t0.acc(100,200))");
输出:
300
对照前面的C#代码,这里的acc实际上是一个委托,指向了add方法,所以实际上执行的是add。
-
Lua访问C#的具体细节参见章节《脚本语言访问C#规范》
-
-
复杂交互
首先定义委托FN
public delegate long FN(object f, long a);
然后执行下面的代码
FN fn = (object f, long a) => (long)lua.eval("if<1><2 then return<1>else return<0>(<0>,<1<-1)+<0>(<0>,<1>-2)end", f, a); lua.eval("print(<0>(<0>,<1>))", fn, 9);
输出:
34
事实上,这段程序在C#和Lua之间间接递归,计算了斐波那契数列的第9项。说明了C#和Lua进行复杂交互的可能性。