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进行复杂交互的可能性。


上一页 下一页