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
【原文參考】
[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