LuaInterface简单介绍
Lua是一种非常好的扩展性语言。Lua解释器被设计成一个非常easy嵌入到宿主程序的库。LuaInterface则用于实现Lua和CLR的混合编程。
(一)Lua from the CLR
測试环境:在VS2010中建一个C#控制台应用程序。并加入LuaInterface.dll的引用(安装LuaForWindows或直接下载LuaInterface都可得到该文件)。
LuaForWindows下载地址:http://luaforge.net/projects/luaforwindows/
LuaInterface下载地址:http://luaforge.net/projects/luainterface/ (下载luainterface-1.5.3,这里面的资源比較多,还有比較完整的演示样例代码。非常实用的哦。)
LuaInterface.Lua类是CLR訪问Lua解释器的主要接口,一个LuaInterface.Lua类对象就代表了一个Lua解释器(或Lua运行环境)。Lua解释器能够同一时候存在多个,而且它们之间是全然相互独立的。
以下的简单代码展示了以下功能:
(1)CLR訪问Lua的全局域: 下标/索引操作[]
(2)CLR新建Lua的table:NewTable
(3)CLR中运行Lua脚本代码或脚本文件:DoFile、DoString
using System; using System.Collections.Generic; using System.Linq; using System.Text; using LuaInterface; namespace TestCSharpAndLuaInterface { static void Main(string[] args) { // 新建一个Lua解释器,每个Lua实例都相互独立 Lua lua = new Lua(); // Lua的索引操作[]能够创建、訪问、改动global域,括号中面是变量名 // 创建global域num和str lua["num"] = 2; lua["str"] = "a string"; // 创建空table lua.NewTable("tab"); // 运行lua脚本。着两个方法都会返回object[]记录脚本的运行结果 lua.DoString("num = 100; print(\"i am a lua string\")"); lua.DoFile("C:\\luatest\\testLuaInterface.lua"); object[] retVals = lua.DoString("return num,str"); // 訪问global域num和str double num = (double)lua["num"]; string str = (string)lua["str"]; Console.WriteLine("num = {0}", num); Console.WriteLine("str = {0}", str); Console.WriteLine("width = {0}", lua["width"]); Console.WriteLine("height = {0}", lua["height"]); } } }
LuaIntrface自己主动相应Lua和CLR中的一些基础类型:
[nil, null]
[string, System.String]
[number, System.Double]
[boolean, System.Boolean]
[table, LuaInterface.LuaTable]
[function, LuaInterface.LuaFunction]
以上相应关系反之亦然。
特殊类型:userdata
(1)CLR中不能自己主动匹配Lua类型的对象(以上基础类型之外的类型)传给Lua时。转换为userdata,当Lua把这些userdata传回给CLR时,这些userdata又转换回原类型对象;
(2)Lua里面生成的userdata从Lua传到CLR时转换为LuaInterface.LuaUserData。
LuaTable和LuaUserData都有索引操作[],用来訪问或改动域值,索引能够为string或number。
LuaFunction和LuaUserData都有call方法用来运行函数。能够传入随意多个參数并返回多个值。
Lua调用CLR的函数:RegisterFunction方法用来将CLR函数注冊进Lua解释器,供Lua代码调用。看以下这个样例:
namespace TestCSharpAndLuaInterface { class TestClass { private int value = 0; public void TestPrint(int num) { Console.WriteLine("TestClass.TestPrint Called! value = {0}", value = num); } public static void TestStaticPrint() { Console.WriteLine("TestClass.TestStaticPrint Called!"); } } class Program { static void Main(string[] args) { Lua lua = new Lua(); TestClass obj = new TestClass(); // 注冊CLR对象方法到Lua,供Lua调用 lua.RegisterFunction("LuaTestPrint", obj, obj.GetType().GetMethod("TestPrint")); // 也可用 typeof(TestClass).GetMethod("TestPrint") // 注冊CLR静态方法到Lua,供Lua调用 lua.RegisterFunction("LuaStaticPrint", null, typeof(TestClass).GetMethod("TestStaticPrint")); lua.DoString("LuaTestPrint(10)"); lua.DoString("LuaStaticPrint()"); } } }
(二)CLR from Lua
(1)载入和实例化CLR类型
測试环境有两种方式:
第一种:纯Lua文件里进行測试
将LuaForWindows安装的LuaInterface.dll和luanet.dll都复制到自己注冊的环境变量的文件夹下,比方我的是"C:\\luatest\\",然后就能够在Lua编辑器中编写測试代码了,例如以下:
--package.cpath = "C:\\luatest\\?.dll" require "luanet" --载入CLR的类型、实例化CLR对象 luanet.load_assembly("System.Windows.Forms") luanet.load_assembly("System.Drawing") Form = luanet.import_type("System.Windows.Forms.Form") StartPosition = luanet.import_type("System.Windows.Forms.FormStartPosition") print(Form) print(StartPosition)
上面的代码演示了假设利用LuaInterface的luanet在Lua中载入CLR的类型。在配置编译环境的时候一定要注意将两个dll同一时候复制到一个文件夹下。由于luanet.dll是依赖LuaInterfce.dll的。
另外一种:在C#project中測试
还是在外部单独编写lua代码文件。然后在C#project中使用lua.DoFile接口执行lua代码。这样的方式比較灵活而且可以更方便的測试LuaInterface所提供的各项功能,我们后面的測试代码均是在这样的模式系下进行測试。
这样的模式下就不须要在lua脚本中手动require "luanet"了,由于已经手动将LuaInterface的引用加入到project中了。lua脚本中直接使用luanet就能够訪问各接口了。
luanet.load_assembly函数:载入CLR程序集;
luanet.import_type函数:载入程序集中的类型;
luanet.get_constructor_bysig函数:显示获取某个特定的构造函数。
c#主要代码例如以下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using LuaInterface; namespace TesLuaInterface { class TestClass2 { public TestClass2(string str) { Console.WriteLine("called TestClass2(string str) str = {0}", str); } public TestClass2(int n) { Console.WriteLine("called TestClass2(int n) n = {0}", n); } public TestClass2(int n1, int n2) { Console.WriteLine("called TestClass2(int n1, int n2) n1 = {0}, n2 = {1}", n1, n2); } } // 载入和实例化CLR类型 static void Main(string[] args) { Lua lua = new Lua(); lua.DoFile("C:\\luatest\\testLuaNet.lua"); } } }
lua主要代码例如以下:
-- 载入自己定义类型,先载入程序集,在载入类型 luanet.load_assembly("TestEnvi") TestClass = luanet.import_type("TesLuaInterface.TestClass2") obj1 = TestClass(2, 3) -- 匹配public TestClass2(int n1, int n2) obj2 = TestClass("x") -- 匹配public TestClass2(string str) obj3 = TestClass(3) -- 匹public TestClass2(string str) TestClass_cons2 = luanet.get_constructor_bysig(TestClass, 'System.Int32') obj3 = TestClass_cons2(3) -- 匹配public TestClass2(int n)
TestEnvi为我建的project代码的程序集名字,这一项是能够在project属性中进行设置的。TestLuaInterface为測试代码的命名空间。
从上面的构造函数的匹配能够看出,LuaInterface匹配构造函数的规律:
LuaInterface匹配第一个可以匹配的构造函数,在这个过程中。numerical string(数字字符串)会自己主动匹配number。而number可以自己主动匹配string。所以TestClass(3)匹配到了參数为string的构造函数。
假设一定要手动匹配某个构造函数,则能够使用luanet.get_constructor_bysic函数。
(2)訪问CLR类型对象的字段和方法
Lua代码中。訪问CLR类型对象的字段的方式和訪问table的键索引一样。比方button1.Text、button["Text"]。
Lua代码中,訪问CLR类型对象的函数的方式和调用table的函数一样,比方form:ShowDialog()。
规则非常easy,但在訪问函数的时候,有下面几种情况须要注意的:
(a)当有重载函数时。函数的匹配过程和上面提到的构造函数的匹配过程一样,自己主动匹配第一个可以匹配的函数。
假设一定要手动调用某个特定參数的函数。可以使用luanet.get_method_bysig函数来制定,比方:
setMethod=get_method_bysig(obj,'setValue','System.String')" setMethod('str')
(b)当函数有out或ref參数时,out參数和ref參数和函数的返回值一起返回,而且调用函数时out參数不须要传入。比方:
-- calling int obj::OutMethod1(int,out int,out int) retVal,out1,out2 = obj:OutMethod1(inVal) -- calling void obj::OutMethod2(int,out int) retVal,out1 = obj:OutMethod2(inVal) -- retVal ser´a nil -- calling int obj::RefMethod(int,ref int)
(c)假设一个对象有两个Interface,而且两个Interface都有某个同名函数比方。IFoo.method()和IBar.method()。这样的情况下obj["IFoo.method"]表示訪问前者。
訪问CLR类型对象的字段和函数的演示样例代码例如以下:
luanet.load_assembly("System.Windows.Forms") luanet.load_assembly("System.Drawing") Form = luanet.import_type("System.Windows.Forms.Form") Button = luanet.import_type("System.Windows.Forms.Button") Point = luanet.import_type("System.Drawing.Point") StartPosition = luanet.import_type("System.Windows.Forms.FormStartPosition") form1 = Form() button1 = Button() button2 = Button() position = Point(10, 10) start_position = StartPosition.CenterScreen button1.Text = "OK" button2["Text"] = "Cancel" button1.Location = position button2.Location = Point(button1.Left, button1.Height + button1.Top + 10) form1.Controls:Add(button1) form1.Controls:Add(button2) form1.StartPosition = start_position form1:ShowDialog()
(3)事件处理,加入和删除事件托付
LuaInterface为Event提供了Add和Remove函数来注冊和移除事件处理函数。Add函数传入一个Lua函数,将其转换为一个CLR托付(delegate),并返回这个托付。
function handle_mouseup(sender,args) print(sender:ToString() .. ’ MouseUp!’) button.MouseUp:Remove(handler) end handler = button.MouseUp:Add(handle_mouseup)
(4)LuaInterface三种扩展CLR的方法
LuaInterface提供了三种扩展CLR的方法,第一种就是上面提到的加入托付的方式,在须要delegate的地方传入Lua function,LuaInterface利用Lua function创建一个CLR delegate 并传入CLR。
另外一种是在须要CLR Interface实例的地方传入一个Lua Table,比方:
-- interface ISample { int DoTask(int x, int y); } -- SomeType.SomeMethod signature: int SomeMethod(ISample i) -- obj is instance of SomeType sum = {} function sum:DoTask(x,y) return x+y end -- sum is converted to instance of ISample res = obj:SomeMethod(sum)
假设Interface里面有多个重载接口,那么Lua Table须要实现每个版本号的接口函数。而且要注意out和ref參数的处理:
-- interface ISample2 { void DoTask1(ref int x, out int y); -- int DoTask2(int x, out int y); } -- SomeType.SomeMethod signature: int SomeMethod(ISample i) -- obj is instance of SomeType inc = {} function inc:DoTask1(x) return x+1,x end function inc:DoTask2(x) return x+1,x end res = obj:SomeMethod(sum)
第三种是利用Lua Table继承CLR Class,也就是用Table作为其子类,这里CLR Class必须拥有virtual函数,而且Lua Table必须至少重写一个virtual函数。主要相关函数是luanet.make_object。
-- class SomeObject { -- public virtual int SomeMethod(int x, int y) { return x+y; } } -- SomeType.SomeMethod signature: int SomeMethod(SomeObject o) -- obj is instance of SomeType some_obj = { const = 4 } function some_obj:SomeMethod(x,y) local z = self.base:SomeMethod(x,y) return z*self.const end SomeObject = luanet.import_type(’SomeObject’) luanet.make_object(some_obj,SomeObject) res = some_obj:SomeMethod(2,3) -- returns 20 res = some_obj:ToString() -- calls base method res = obj:SomeMethod(some_obj) -- passing as argument
由于Table作为子类实例,那么就能够在须要Class的地方传入这个Table实例。
注意。假设Table没有重写不论什么virtual函数,则直接返回父类对象。
当然。作为子类,能够直接訪问父类中其它的还接口。
以上三种归纳起来就是:Lua Function-->CLR delegate、Lua Table-->CLR Interface、 Lua Table-->CLR Class。