黃皓(中山市廣播電視大學(xué),中山 528400)
VB與Lua交互調(diào)用的研究與實現(xiàn)
黃皓
(中山市廣播電視大學(xué),中山 528400)
VB和Lua都是當(dāng)今廣為流行的程序設(shè)計語言。Lua是簡潔、輕量、可擴(kuò)展的腳本語言,1993年誕生于巴西里約熱內(nèi)盧天主教大學(xué)。Lua的優(yōu)點在于:可嵌入、跨平臺、運(yùn)行高效、語法簡潔、免費開源、小巧輕便。近年來,Lua除了游戲開發(fā)以外,還廣泛地應(yīng)用于其他領(lǐng)域。
VB是Microsoft的可視化、基于對象和采用事件驅(qū)動方式的快速應(yīng)用程序開發(fā) (RAD)工具。它源自于BASIC語言,簡單易學(xué);擁有圖形用戶界面(GUI),可使用Win32 API函數(shù)、動態(tài)鏈接庫(DLL)、對象的鏈接與嵌入(OLE)、開放式數(shù)據(jù)連接(ODBC)等技術(shù),功能強(qiáng),開發(fā)效率高。
VB和Lua各有優(yōu)勢,如能取長補(bǔ)短,互為補(bǔ)充,則在程序設(shè)計中可以更高效、靈活地達(dá)成目標(biāo)。例如使用VB來進(jìn)行GUI界面設(shè)計、訪問數(shù)據(jù)庫,而Lua進(jìn)行程序配置和字符串的處理等。
Lua是用C編寫的,在設(shè)計時就以嵌入宿主語言C/C++程序為目標(biāo),因此其與宿主語言的交互由一系列C API構(gòu)成,所有的API在lua.h、lauxlib.h、lualib.h三個C頭文件中定義。VB的數(shù)據(jù)類型以及函數(shù)使用方式與
C相似,其與Lua的交互調(diào)用可以參照C/C++的方式。
Lua與宿主程序通過一個虛擬的堆棧進(jìn)行數(shù)據(jù)交換。缺省的堆棧大小由LUA_MINSTACK定義,一般為20,可以使用lua_checkstack函數(shù)來擴(kuò)大可用堆棧的尺寸。Lua遵循FILO規(guī)則使用堆棧。而宿主語言可以通過索引使用棧中元素。索引值為正表示棧中的絕對位置(從1開始);索引值為負(fù)則指從棧頂開始的偏移量。如堆棧有n個元素,那么索引1或-n表示第一個被壓入堆棧的元素(棧底),而索引n或-1則指最后一個元素(棧頂)。索引index在1到棧頂之間有效,0不是有效的索引值。
(1)壓入堆棧
lua_getglobal(lua_State*L,const char*name)
把全局變量name里的值壓入堆棧。
lua_push*將C程序中的數(shù)據(jù)放入棧中。(*可以是nil,number,integer,string,boolean,userdata,thread,cclosure等,下同)
(2)彈出堆棧
lua_pop(lua_State*L,int n);
從堆棧中彈出n個元素。此操作僅修改棧頂位置,并不能得到相應(yīng)數(shù)據(jù)。
(3)棧元素查詢
返回給定索引處的值的類型,返回值作為常量定義在lua.h中,如LUA_TNUMBER等。
lua_is*當(dāng)對象與所給類型兼容的時候函數(shù)返回1,其他情況返回0。除了lua_isboolean,它只針對布爾值時才會成功,否則將是無用的。這些函數(shù)對于無效引用返回0。
(4)從棧中取元素
使用lua_to*將指定的索引處的的Lua類型值轉(zhuǎn)換為一個C中的值。此操作并不修改棧頂位置。
通過兩個步驟來實現(xiàn):
①Lua全局變量壓入堆棧:lua_getglobal(L,"變量名");
②根據(jù)棧頂數(shù)據(jù)的數(shù)據(jù)類型lua_type,取棧頂數(shù)據(jù):lua_to*
可以有三種調(diào)用方式:
(1)執(zhí)行一個語句塊
Lua API直接支持執(zhí)行一個語句塊,使用luaL_dostring(L,“Lua語句塊”)
(2)執(zhí)行一個文件
①將磁盤中的Lua源程序文件裝入:luaL_loadfile (L,fn);
②以保護(hù)方式執(zhí)行該塊:lua_pcall(L,0,0,0);
在執(zhí)行前,Lua會對源文件進(jìn)行編譯,生成中間代碼。
(3)調(diào)用文件中的函數(shù)
Lua的函數(shù)和普通變量一樣也是First Class Variable,可以看作函數(shù)指針變量參與棧操作。因此調(diào)用過程分為如下幾個步驟:
①被調(diào)用的Lua函數(shù)變量入棧;
②將函數(shù)需要的參數(shù)入棧,入棧順序按照參數(shù)被聲明的順序;
③告知Lua虛擬機(jī)入棧參數(shù)的個數(shù)、函數(shù)返回值的個數(shù),并調(diào)用此Lua函數(shù);
④從棧頂獲得返回值,先返回的先入棧,然后將返回值顯式出棧。
(1)注冊宿主子程序
①聲明并定義一個C函數(shù),函數(shù)原型為typedef int (*lua_CFunction)(lua_State*L),該函數(shù)有一個參數(shù),而函數(shù)的返回值為壓入虛擬堆棧的數(shù)據(jù)個數(shù);
②用字符串給該C函數(shù)取一個在Lua中調(diào)用的名稱,壓入堆棧;
③將函數(shù)指針入棧;
④調(diào)用Lua API,將上述的名稱與函數(shù)指針關(guān)聯(lián)。在VB中,第①步定義的函數(shù)應(yīng)該為如下形式:
'從虛擬棧中取參數(shù);參數(shù)是按左右順序壓入堆棧的。'處理;
'將結(jié)果按返回順序壓入虛擬棧;
第③步中,可以使用AddressOf myFun來取得VB子程序的地址,而VBS沒有AddressOf,所以VBS不能注冊子程序并讓其回調(diào)。
(2)在Lua中調(diào)用執(zhí)行
Lua使用注冊的函數(shù)名來調(diào)用宿主子程序。當(dāng)調(diào)用宿主C函數(shù)時,Lua使用一個獨立的新棧,其中包含了Lua傳遞給C函數(shù)的所有參數(shù),而C函數(shù)則把要返回的結(jié)果也放入堆棧以返回給調(diào)用者。被調(diào)用的C函數(shù)不能訪問Lua虛擬機(jī)本次調(diào)用之外的堆棧中的數(shù)據(jù)。
一般來說,代碼復(fù)用有靜態(tài)編譯和動態(tài)鏈接兩種方式。Lua是開源的,如果宿主程序使用C/C++語言編寫,那么可以將Lua與宿主源程序一同編譯,Lua與宿主合為一個整體。VB顯然不能采用這一方式。
宿主程序也可以動態(tài)鏈接Lua庫中的函數(shù)。在這種方式下Lua先獨立編譯為DLL文件,然后宿主程序在運(yùn)行時與Lua動態(tài)鏈接。VB以下面的格式使用DLL中的API:
Declare function函數(shù)名稱libs“動態(tài)鏈接庫文件路徑”alias“庫函數(shù)名”(參數(shù)列表)as返回類型
從www.lua.org下載Lua源程序包后,編譯(也可直接在網(wǎng)上下載)得到Lua.dll動態(tài)鏈接庫,然后把lua.h、lauxlib.h、lualib.h三個C頭文件中定義的API信息轉(zhuǎn)換為VB可用的模塊文件,在需要使用Lua的VB工程中導(dǎo)入模塊。
(1)VB使用的模塊文件
Lua頭文件中定義了許多API,在下面的模塊文件中,僅引入供后面類模塊必須使用的API。由于Lua各個版本之間存在差異,頭文件與相應(yīng)的DLL對應(yīng),不能混用。本模塊以Lua5.1版本為例。
以下是模塊文件Mudule1.bas應(yīng)該包含的內(nèi)容:
Public Declare Sub CopyMemory Lib"kernel32.dll"Alias" RtlMoveMemory"(Destination As Any,Source As Any,ByVal Length As Long)
該Win32 API用于后面的lua_tostring函數(shù)。
在模塊中還用到了lua.h頭文件中定義的幾個常量。例如:
#define LUA_GLOBALSINDEX(-10002)
在VB中轉(zhuǎn)換為:
Public Const LUA_GLOBALSINDEX As Long=(-10002)
在lua.h中找到LUA_MULTRET、LUA_TBOOLEAN、LUA_TNUMBER、LUA_TSTRING參照以上方式進(jìn)行定義。
模塊使用了一些Lua常用的API,如:
LUALIB_API void(luaL_openlibs)(lua_State*L);
在VB中轉(zhuǎn)換為:
Public Declare Function luaL_openlibs Lib"lua"(ByVal h As Long)As Long
在 lua.h中找到 lua_close、lua_getfield、lu a_setfield、lua_pcall、lua_pushboolean、lua_pushnumber、lua_pushstring、lua_pushcclosure、lua_settable、lua_type、lua_toboolean、lua_tonumber、lua_tolstring、lua_settop,在lauxlib.h中找到 luaL_newstate、luaL_loadstring、luaL_loadfile,參照上述方式進(jìn)行定義。注意函數(shù)返回類型除了lua_tonumber為Double外,其余均為Long;參數(shù)類型除了const char*應(yīng)定義為String外,其余均定義為Long;參數(shù)調(diào)用方式為ByVal按值調(diào)用。
在lua.h還定義了一些宏,例如:
#define lua_getglobal(L,s)lua_getfield(L,LUA_GLOBALSINDEX,(s))
這些API在Lua.dll中無導(dǎo)出函數(shù),VB需用函數(shù)來實現(xiàn):
Public Function lua_getglobal(ByVal h As Long,s As String)As Long
lua_getglobal=lua_getfield(h,LUA_GLOBALSINDEX,s)End Function
參照以上方法用VB對 lua_setglobal、lua_pushcfunction、lua_pop編寫相應(yīng)函數(shù)。而lua_tostring處理相對較多,對應(yīng)VB函數(shù)如下:
在lauxlib.h中還有一個要用的API,對應(yīng)VB函數(shù)如下:
(2)VB使用的類模塊
Lua提供的API很多,對于宿主程序來說,未必會使用到全部的功能,最核心的無非是調(diào)用Lua函數(shù)、Lua調(diào)用宿主函數(shù)、運(yùn)行Lua語句塊、訪問Lua全局變量等交互。因此,為了方便進(jìn)行處理,我們可以建立以下類模塊。限于篇幅,類模塊將處理的數(shù)據(jù)類型縮減為數(shù)值、字符串和布爾三種常用類型,實際應(yīng)用時可根據(jù)需要進(jìn)行增加。
下面是類模塊文件Class1.cls的主要內(nèi)容:
Dim L As Long'Lua虛擬機(jī)指針
Public Function LuaOpen()As Long'新建虛擬機(jī),代碼略
Public Function LuaClose()As Long'關(guān)閉虛擬機(jī),代碼略
Public Function LuaPushValue(ByVal value As String)As Long'根據(jù)參數(shù)字串內(nèi)容壓入數(shù)據(jù)數(shù)值、字串或布爾值
If LCase(value)="false"Then
LuaPushValue=lua_pushboolean(L,0)
ElseIf LCase(value)="true"Then
LuaPushValue=lua_pushboolean(L,1)
Else
LuaPushValue=lua_pushstring(L,value)
End If
Else
LuaPushValue=lua_pushnumber(L,v)
End If
End Function
Public Function LuaPopValue()As String'返回棧頂內(nèi)容字串
Select Case lua_type(L,-1)
Case LUA_TNUMBER
LuaPopValue=Str(lua_tonumber(L,-1))
Case LUA_TSTRING
LuaPopValue=lua_tostring(L,-1)
Case LUA_TBOOLEAN
LuaPopValue=IIf(lua_toboolean(L,-1),"True"," False")
End Select
lua_pop L,1
End Function
Public Function LuaGetVar(ByVal varname As String)As String'取變量內(nèi)容,代碼略
Public Function LuaSetVar(ByVal varname As String,By-Val value As String)As Long'設(shè)置變量值,代碼略
Public Function LuaDoString(ByVal s As String)As Long'執(zhí)行字符串中的語句塊,代碼略
Public Function LuaDoFile(ByVal fn As String)As Long'執(zhí)行文件,代碼略
Public Function LuaCall(ByVal fun As String,ByVal para As String,ByVal nRes As Long)As String'調(diào)用Lua中的函數(shù)
Dim iErr As Long,nPara As Long,A ()As String,i As Long
lua_getglobal L,fun
A=Split(para,",")'參數(shù)para用“,”分隔
nPara=UBound(A)
If nPara<0 Then nPara=0:ReDim A (0):A(0)= para
For i=0 To nPara:Call LuaPushValue(A(i)):Next
iErr=lua_pcall(L,nPara+1,nRes,0)'nRes為結(jié)果個數(shù)
LuaCall=""
For i=1 To nRes
LuaCall=LuaPopValue()+","+LuaCall
Next
LuaCall=Left(LuaCall,Len(LuaCall)-1)
End Function
Public Function LuaReg(ByVal fun As String,ByVal lp As Long)As Long'注冊VB函數(shù),lp為VB函數(shù)指針
lua_pushstring L,fun
lua_pushcfunction L,lp
LuaReg=lua_settable(L,LUA_GLOBALSINDEX)
End Function
(3)實例
將Lua.dll復(fù)制到System32目錄中,新建工程“標(biāo)準(zhǔn)EXE”,導(dǎo)入模塊Mudule1.bas和類模塊Class1.cls(命名為LuaClass),就可以在VB程序中使用Lua了。如在窗體Form1中,新建一個按鈕Command1,輸入以下代碼:
Private Sub Command1_Click()
Dim result As String
o.LuaOpen
o.LuaReg"vb",AddressOf ex
o.LuaDoString("function f1(a,b)x,y=vb(a,b)return x,y end c=math.sin(1/2);")
result=o.LuaCall("f1","100,200",2)
MsgBox result,,o.LuaGetVar("c")
o.LuaClose
End Sub
新建一個模塊,輸入以下代碼:
Public o As New LuaClass
Function ex(ByVal L As Long)As Long
Dim x As Long,y As Long
x=o.LuaPopValue:y=o.LuaPopValue
o.LuaPushValue x:o.LuaPushValue y:ex=2
End Function
運(yùn)行并點擊Command1,可得到運(yùn)行結(jié)果。
ASP、WSH、VBS等腳本語言不能直接使用DLL,我們可以將上述模塊封裝為COM組件,那么這些高級語言就可以通過COM使用Lua來進(jìn)行擴(kuò)展了。
在VB中新建工程“ActiveX DLL”,導(dǎo)入模塊Mud-ule1.bas和類模塊Class1.cls(命名為LuaClass),在菜單“工程”-“屬性”處,更改工程名為“Lua”,點擊“文件”-“生成dll”。假設(shè)生成c:LuaX.dll,運(yùn)行regsvr32 c:luaX. dll,復(fù)制Lua.dl1到System32中,然后上述腳本語言就可以通過COM使用Lua了。如建立并運(yùn)行如下腳本文件Test.vbs:
Set o=CreateObject("Lua.LuaClass")
o.LuaOpen
o.LuaDoString("function f1(a,b)return b,a end c=string. sub(math.sin(1/2),1,8)")
x=o.LuaGetVar("c"):s=o.LuaCall("f1","100,200",2)
msgbox s,,x
o.LuaClose
在國外TIOBE軟件廠商發(fā)布2014年7月份編程語言排行榜中,VB由2013年的第7位升高到第5位,占4.341%;而Lua排名36位,占0.332%。這說明即使在Microsoft轉(zhuǎn)向.NET平臺多年以后,VB依然受到編程者的喜愛,而Lua的發(fā)展?jié)摿艽?。在VB中調(diào)用Lua,結(jié)合VB的快速開發(fā)特性和Lua的靈活性,我們開發(fā)的應(yīng)用將更有彈性,而對于Lua接口API的再封裝,無疑為ASP等腳本語言使用Lua打開了一扇大門。
[1]Roberto Ierusalimschy.Lua程序設(shè)計[M].2版.周惟迪,譯.北京:電子工業(yè)出版社,2008.
[2]鄧正陽,陳和平,蘇鵬.動態(tài)腳本語言Lua與C++交互方法的研究與實現(xiàn)[J].計算機(jī)系統(tǒng)應(yīng)用,2010,19(5):198-201.
[3]魏江平.LUA腳本語言在游戲引擎中的應(yīng)用分析[J].微型電腦應(yīng)用,2008,24(4):22-23.
[4]Lablua at PUC-Rio.Lua 5.1 Reference Manual[EB/OL].(2011-09-28)[2014-1-28].http://www.lua.org/manual/5.1/.
[5]張帆.可重構(gòu)軟件平臺構(gòu)建原理與應(yīng)用研究[D].武漢理工大學(xué),2012.
[6]鄧楠喬,秦開宇,金燕華.基于Lua的面向組件程序設(shè)計研究[J].中國高新技術(shù)企業(yè),2010,(9):3-4.
[7]Marco Pontello.PowerBLua-A Lua5.0 wrapper for PowerBASIC[EB/OL].(2007-08-03).http://mark0.net/code-powerblua-e.html.
[8]汪君鵬,李宥謀.基于Lua腳本技術(shù)的網(wǎng)絡(luò)化測控系統(tǒng)設(shè)計[J].西安郵電大學(xué)學(xué)報,2013,18(1):90-94.
[9]苗新亮,劉棟,雷波,楊遠(yuǎn)輝.Lua腳本語言在安全設(shè)備管理系統(tǒng)中的應(yīng)用[J].信息安全與通信保密,2014,(2):92-94.
[10]孫秀梅,鞏建華.Visual Basic開發(fā)實戰(zhàn)1200例(第1卷)[M].北京:清華大學(xué)出版社,2011.
VB;Lua;COM
Research and Implementation of Interaction Between VB and Lua
HUANG Hao
(Zhongshan Radio&Television University,Zhongshan 528400)
1007-1423(2015)30-0068-05
10.3969/j.issn.1007-1423.2015.30.019
黃皓(1969-),男,廣西桂林人,講師,碩士,研究方向為計算機(jī)程序設(shè)計、遠(yuǎn)程教育技術(shù)
2015-09-15
2015-09-30
VB和Lua都是常用的編程語言,為了結(jié)合利用VB和Lua各自的優(yōu)點,快速、高效、靈活地進(jìn)行應(yīng)用程序開發(fā),通過對C與Lua之間交互模式的研究分析,提出VB與Lua交互調(diào)用的方法,并進(jìn)一步將VB調(diào)用Lua的接口封裝為COM組件,以隱藏細(xì)節(jié)信息和簡化調(diào)用接口,可支持更多其他編程語言如ASP、VBS等調(diào)用Lua。實踐證明,這種VB 與Lua的交互調(diào)用方式是有效的、易于使用的。
VB;Lua;COM
VB and Lua are commonly used programming language,in order to take the advantage of VB and Lua and develop application fast,efficiently and flexibly,analyses the interactive mode between C and Lua,then presents a method for VB to call Lua,finally discusses about packaging the interface into a COM object,which hides detail information and simplifies the interface,and supports more programming languages to use Lua.Practice has proved that the method of VB interacting with Lua is effective,easy to use.