天天看点

LuaInterface用户手册

1.介绍

LuaInterface用来集成lua语言和.netCLR。许多语言都已经针对CLR的编译器了,CLR已经实现了MircrosoftWindows、BSD和Linux操作系统。

Lua是一个为扩展应用程序而设计的编程语言,解释执行,很容易嵌入的库。详细的信息可以参考Lua'sreference manual。

下面的部分介绍怎样编译和安装LuaInterface。第3部分包括了在CLR应用程序中使用它,第4部分介绍Lua脚本中的使用。

2.安装LuaInterface

LuaInterface需要Lua解释器来工作。Lua5.0的一个解释器已经包含在LuaInterface发布包中,包含Mircrosoft Windows下的LuaInterface二进制文件和.NETCLR(LuaInterface.dll和luanet.dll)。你必须拷贝lua50.exe和lua50.dll到你的PATH目录下,拷贝LuaInterface.dll到你的全局Assembly缓存。LuaInterface使用Compat-5.1,因此拷贝luanet.dll到你的package.cpath下。

从源码编译LuaInterface不困难。发布包包含一个用来编译luanet的工程文件,其是在Visual Studio .Net 2003下的,用来编译的编译脚本是在Visual Studio 6下的。你可以简单的编译所有src/LuaInterface下的c#文件来得到LuaInterface.dll。

3.在CLR下使用Lua

CLR应用程序通过LuaInterface.Lua类来使用Lua解释器。实例化这个类,创建一个新的Lua解释器,不同实例直接完全独立。

Lua类索引创建,读取和修改全部变量,由变量的名字索引,如:

//Start a Lua interpreter

Lualua = new Lua();

//Create global variables "num" and "str"

lua["num']= 2;

lua["str']= "a string";

//Create an empty table

lua.NewTable("tab");

//Read global variable "num and "str"

doublenum = (double)lua["num"];

stringstr = (string)lua["str"];

DoString和DoFile方法执行Lua脚本。当脚本返回值时,这个方法返回一个数组,如:

//Execute a Lua script file

lua.DoFile("script.lua");

//Execute chunks of Lua code

lua.DoString("num=2");

lua.DoString("str='astring'");

//Lua code returning values

object[]retVals = lua.DoString("return num,str");

LuaInterface自动转换Lua的nil到CLR的null,strings到System.String,numbers到System.Double,booleans到System.Boolean,tables到LuaInterface.LuaTable,functions到LuaInterface.LuaTable,反之亦然。Userdata是一种特殊情况:CLRobjects没有匹配的Lua类型,userdata转换回原类型当传递给CLR时。LuaInterface转换其它userdata到LuaInterface.LuaUserData.

LuaTable和LuaUserData对象有索引来读取和修改字段,使用字符串或数字进行索引。LuaFunction和LuaUserData对象包含一个Call方法来执行函数,包含参数个数,返回值在一个数组中。

最后,Lua类有一个RegisterFunction函数来注册CLR函数作为一个全局Lua函数。它的参数包含函数名字、目标函数和表示方法的MethodInfo,如lua.RegisterFunction("foo",obj,obj.GetType().GetMethod("Foo"))注册了object obj的Foo方法作为foo函数。

4.lua下使用CLR

这个部分包含在Lua脚本初始化和使用CLR对象,或通过Lua解释器执行,或在CLR应用程序中执行。下面的所有例子都是Lua语言。

4.1加载CLR类型和实例化对象

为了实例化对象,脚本需要类型引用。使用静态字段、调用静态方法同样也需要类型引用。为了获得类型引用,脚本文件首先需要加载一个assembly包含指定类型,通过load_assembly函数。然后使用import_type函数来获得引用。下面的例子显示了怎样使用这两个函数:

require"luanet"

--Loadsthen System.Windows.Forms and System.Drawing assemblies

luanet.load_assembly("System.Windows.Forms")

luanet.load_assembly("System.Drawing")

Form= luanet.import_type("System.Windows.Forms.Form")

Point= luanet.import_type("System.Drawing.Point") --structure

--Loading an enumeration

StartPosition= luanet_import_type("System.Windows.Forms.FormStartPosition")

调用类型应用实例化一个对象。LuaInterface使用第一个满足参数个数和类型的构造函数,由于有重载构造函数存在,匹配过程会转换数字字符串到数字,数字到字符串,如果必要,Lua中的数字参数同样转换到对应CLR数字类型。

下面的例子战士了不同的实例化CLR对象的不同方式。

--SomeType is a reference to a type with following constructors

--1. SomeType(String)

--2.SomeType(int)

--3.SomeType(int,int)

obj1= SomeType(2,3) -- instantiates SomeType with constructor 3

obj2= SomeType("x") -- instantiates SomeType with constructor 1

obj3= SomeType(3) -- instantiates SomeType with constructor 1

Int32= import_type("System.Int32")

--Gets the SomeType constructor with signature (Int32)

SomeType_cons2= get_constructor_bysig(SomeType,Int32)

obj3= SomeType_cons2(3) -- instantiates SomeType with constructor 2

4.2使用字段和方法

脚本中可以使用CLR对象的字段,语法和从table中索引数据一样。写入字段的数据被转换为了对应字段的类型,赋给Int32字段的数字转换为了Int32.没有被索引的属性也像字段一样使用。

LuaInterface有一个简单的索引一维数组的方式,如arr[3].多维数组需要使用Array类的方法。

脚本可以调用对象的方法,语法和调用table的方法一样,传递对象为第一个参数,使用‘:’操作符.可以使用Get和Set方法来使用索引属性。

--button1,button2 and form1 are CLR objects

button1.Text = “OK”;

button2.Text = “Cancel”;

form1.Controls:Add(button1);

form1.Controls:Add(button2);

from1:ShowDialog();

Lua只有值参数的函数调用,因此当脚本调用一个使用了out或ref参数的方法时,LuaInterface返回这些参数的输出值,和方法的返回值一起。下面的例子中,out参数应该被省略。

--calling int obj::OutMethod1(int,out int,out int)

retVal,out1,out2=obj:OutMethod1(intVal)

--calling void obj::OutMethod2(int,out int)

retVal,out1=obj:OutMethod2(inVal) –retValsera nil

--calling int obj:RefMethod(int,ref int)

retVal,ref1=obj:RefMethod(inVal,ref1)

如果一个方法被重载,第一个匹配参数数目、类型的版本会被调用,忽略out参数。下面的例子展示了一个脚本怎么调用不同版本的SomeType的重载SomeMethod方法。

--Versions of SomeType.SomeMethod

--1.void SomeMethod(int)

--2.void SomeMethod(string)

--3.void SomeMethod(OtherType)

--4.void SomeMethod(string,OtherType)

--5.void SomeMethod(int,OtherType)

--6.void SomeMethod(int,OtherTypeSubtype)

--obj1 is instance of SomeType

--obj2 is instance of OtherType

--obj3 is instance of OtherTypeSubtype

obj1:SomeMethod(2) –version 1

obj1:SomeMethod(2.5) – version 1, rounddown

obj1:SomeMethod(“2”) – version 1, convertsto int

obj1:SomeMethod(“x”) – version 2

obj1:SomeMethod(obj2) – version 3

obj1:SomeMethod(“x”,obj2) – version 4

obj1:SomeMethod(2,obj2) – version 4

obj1:SomeMethod(2.5,obj2) – version 4

obj1:SomeMethod(2,obj3) – version 4, cast

--versions 5 and 6 never get called

有个函数get_method_bysig用来防止方法的版本从来不被调用。给出对象及类型和一个方法标志,如下:

--version of SomeType.SomeMethod:

--5.void SomeMethod(int,OtherType)

--obj1 is instance of SomeType

--obj2 is instance of OtherType

Int32 = luanet.import_type(‘System.Int32’)

SomeMethod_sig5 =luanet.get_method_bysig(obj1,’SomeMethod’,Int32,obj2:GetType())

SomeMethod_sig5(obj1,2,obj2) – calls version5

如果一个方法或字段名称是Lua的保留关键字,脚本仍然能使用它们,通过obj[“name”]语法。如果一个对象有2个相同方法属于不同接口,如IFoo.method()和IBar.method(),obj[“IFoo.method”](obj)调用第一个版本。

LuaInterface在执行方法发生错误时会抛出异常,以带错误信息的异常对象。如果脚本想捕获错误,必须用pcall来调用所有方法。

4.3处理事件

LuaInterface中的事件有个一个Add和一个Remove方法,分别用来注册和取消注册事件处理。Add以Lua方法为参数,转换它到CLR对应托管方法并返回。Remove以事件处理托管为参数,移除处理器,如下:

function handle_mouseup(sender,args)

        print(sender:ToString().. ‘ MouseUp!’)

        button.MouseUp:Remove(handler)

end

handler =button.MouseUp:Add(handle_mouseup)

脚本同样可以使用事件对象的add和remove方法来注册事件处理,但是add不返回托管对象,因此函数以这种方式注册不能取消注册。

4.4托管和子类化

LuaInterface提供3中动态创建类型的方法来扩展CLR。第一种已经在事件处理中已经论述了。

第二种方法是传递一个Lua table,其中实现了接口。LuaInterface创建一个新的接口实现的类型,这个类型的对象方法托管到table,如下:

--interface ISample{int DoTask{int x, inty}; }

--SomeType.SomeMethod signature: intSomeMethod(ISample i)

--obj is instance of SomeType

sum={}

function sum:DoTask(x,y)

        returnx+y

end

--sum is convert to instance of ISample

Res=obj:SomeMethod(sum)

如果接口中有重载函数,所有函数都会托管到一个Lua函数,这个函数根据参数类型确定哪个版本的被调用。LuaInterface不能传递out参数给函数,但是函数必须一起返回这些值和ref参数的输出值,如下:

--interface ISample2 {void DoTask1(ref intx, out int y);

                         VoidDoTask2(int x, out int y); }

--SomeType.SomeMethod signature:intSomeMethod(ISample i)

--obj is instance of SomeType

inc={}

function inc:DoTask1(x)

        returnx+1,x

end

function inc:DoTask2(x)

        returnx+1,x

end

res=obj:SomeMethod(sum)

最后一种创建新CLR类型的方式是子类化已经存在的类,用Lua table的函数来重写一些或所有它的virtual方法。table函数调用父类的函数通过一个名字为base的字段。

为了将一个table变成一个子类的实例,脚本必须调用make_object函数,参数为talbe和类的类型,如下:

--class SomeObject{

--public virtual int SomeMethod(int x, inty) { return x+y; }}

--SomeType.SomeMethod signature:intSomeMethod(SomeObject o)

--obj is instance of SomeType

some_obj = {const=4}

function some_obj:SomeMethod(x,y)

        localz=self.base:SomeMethod(x,y)

        returnz*self.const

end

SomeObject=luanet.import_type(‘SomeObject’)

Luanet.make_object(some_obj,SomeObject)

res =some_obj:SomeMethod(2,3) –return 20

res=some_obj:ToString() –calls base method

res =obj:SomeMethod(some_obj) –passing asargument

在子类化过程中,关于重载和out/ref参数存在同样的问题。最后,free_object函数以前面make_object调用的table参数,服务table和CLR子类实例的连接。脚本必须在丢弃table的引用前使用这个方法,不然会造成内存泄露。

【原文来自于NLua源码的doc目录下的guid.pdf

LuaInterface:User’s Guide

Fabio Mascarenhas

Departmento de informatica,PURC-Rio

Rua Marques de Sao Vicente,225-22453-900

Rio de Janeiro,RJ,Brasil

[email protected]】

【原文参考】

[1] R.Ierusalimschy,L.H.Figueiredo,andW.Celes.Lua 5.0 Reference Manual.Technicla Report 14/03,

PUC-Rio,2003.Available athttp://www.lua.org.

[2]E.Meijer and J.Gough.Technical Overviewof the Common Language Runtime.Technical report,Microsoft Research,2002.Availableat http://research .microsoft.com/~emeijer/Papers/CLR.pdf

[3]D.Stutz.The Microsoft Shared Source CLIImplementation,2002.Available athttp://msdn.microsoft.com/library/en-us/Dndotnet/html/mssharesourcecli.asp.

[4]Ximian.The Mono Project,2003.Availableathttp://www.go-mono.com/.

【译者】

无脸男371545207