黄皓(中山市广播电视大学,中山 528400)
VB与Lua交互调用的研究与实现
黄皓
(中山市广播电视大学,中山 528400)
VB和Lua都是当今广为流行的程序设计语言。Lua是简洁、轻量、可扩展的脚本语言,1993年诞生于巴西里约热内卢天主教大学。Lua的优点在于:可嵌入、跨平台、运行高效、语法简洁、免费开源、小巧轻便。近年来,Lua除了游戏开发以外,还广泛地应用于其他领域。
VB是Microsoft的可视化、基于对象和采用事件驱动方式的快速应用程序开发 (RAD)工具。它源自于BASIC语言,简单易学;拥有图形用户界面(GUI),可使用Win32 API函数、动态链接库(DLL)、对象的链接与嵌入(OLE)、开放式数据连接(ODBC)等技术,功能强,开发效率高。
VB和Lua各有优势,如能取长补短,互为补充,则在程序设计中可以更高效、灵活地达成目标。例如使用VB来进行GUI界面设计、访问数据库,而Lua进行程序配置和字符串的处理等。
Lua是用C编写的,在设计时就以嵌入宿主语言C/C++程序为目标,因此其与宿主语言的交互由一系列C API构成,所有的API在lua.h、lauxlib.h、lualib.h三个C头文件中定义。VB的数据类型以及函数使用方式与
C相似,其与Lua的交互调用可以参照C/C++的方式。
Lua与宿主程序通过一个虚拟的堆栈进行数据交换。缺省的堆栈大小由LUA_MINSTACK定义,一般为20,可以使用lua_checkstack函数来扩大可用堆栈的尺寸。Lua遵循FILO规则使用堆栈。而宿主语言可以通过索引使用栈中元素。索引值为正表示栈中的绝对位置(从1开始);索引值为负则指从栈顶开始的偏移量。如堆栈有n个元素,那么索引1或-n表示第一个被压入堆栈的元素(栈底),而索引n或-1则指最后一个元素(栈顶)。索引index在1到栈顶之间有效,0不是有效的索引值。
(1)压入堆栈
lua_getglobal(lua_State*L,const char*name)
把全局变量name里的值压入堆栈。
lua_push*将C程序中的数据放入栈中。(*可以是nil,number,integer,string,boolean,userdata,thread,cclosure等,下同)
(2)弹出堆栈
lua_pop(lua_State*L,int n);
从堆栈中弹出n个元素。此操作仅修改栈顶位置,并不能得到相应数据。
(3)栈元素查询
返回给定索引处的值的类型,返回值作为常量定义在lua.h中,如LUA_TNUMBER等。
lua_is*当对象与所给类型兼容的时候函数返回1,其他情况返回0。除了lua_isboolean,它只针对布尔值时才会成功,否则将是无用的。这些函数对于无效引用返回0。
(4)从栈中取元素
使用lua_to*将指定的索引处的的Lua类型值转换为一个C中的值。此操作并不修改栈顶位置。
通过两个步骤来实现:
①Lua全局变量压入堆栈:lua_getglobal(L,"变量名");
②根据栈顶数据的数据类型lua_type,取栈顶数据:lua_to*
可以有三种调用方式:
(1)执行一个语句块
Lua API直接支持执行一个语句块,使用luaL_dostring(L,“Lua语句块”)
(2)执行一个文件
①将磁盘中的Lua源程序文件装入:luaL_loadfile (L,fn);
②以保护方式执行该块:lua_pcall(L,0,0,0);
在执行前,Lua会对源文件进行编译,生成中间代码。
(3)调用文件中的函数
Lua的函数和普通变量一样也是First Class Variable,可以看作函数指针变量参与栈操作。因此调用过程分为如下几个步骤:
①被调用的Lua函数变量入栈;
②将函数需要的参数入栈,入栈顺序按照参数被声明的顺序;
③告知Lua虚拟机入栈参数的个数、函数返回值的个数,并调用此Lua函数;
④从栈顶获得返回值,先返回的先入栈,然后将返回值显式出栈。
(1)注册宿主子程序
①声明并定义一个C函数,函数原型为typedef int (*lua_CFunction)(lua_State*L),该函数有一个参数,而函数的返回值为压入虚拟堆栈的数据个数;
②用字符串给该C函数取一个在Lua中调用的名称,压入堆栈;
③将函数指针入栈;
④调用Lua API,将上述的名称与函数指针关联。在VB中,第①步定义的函数应该为如下形式:
'从虚拟栈中取参数;参数是按左右顺序压入堆栈的。'处理;
'将结果按返回顺序压入虚拟栈;
第③步中,可以使用AddressOf myFun来取得VB子程序的地址,而VBS没有AddressOf,所以VBS不能注册子程序并让其回调。
(2)在Lua中调用执行
Lua使用注册的函数名来调用宿主子程序。当调用宿主C函数时,Lua使用一个独立的新栈,其中包含了Lua传递给C函数的所有参数,而C函数则把要返回的结果也放入堆栈以返回给调用者。被调用的C函数不能访问Lua虚拟机本次调用之外的堆栈中的数据。
一般来说,代码复用有静态编译和动态链接两种方式。Lua是开源的,如果宿主程序使用C/C++语言编写,那么可以将Lua与宿主源程序一同编译,Lua与宿主合为一个整体。VB显然不能采用这一方式。
宿主程序也可以动态链接Lua库中的函数。在这种方式下Lua先独立编译为DLL文件,然后宿主程序在运行时与Lua动态链接。VB以下面的格式使用DLL中的API:
Declare function函数名称libs“动态链接库文件路径”alias“库函数名”(参数列表)as返回类型
从www.lua.org下载Lua源程序包后,编译(也可直接在网上下载)得到Lua.dll动态链接库,然后把lua.h、lauxlib.h、lualib.h三个C头文件中定义的API信息转换为VB可用的模块文件,在需要使用Lua的VB工程中导入模块。
(1)VB使用的模块文件
Lua头文件中定义了许多API,在下面的模块文件中,仅引入供后面类模块必须使用的API。由于Lua各个版本之间存在差异,头文件与相应的DLL对应,不能混用。本模块以Lua5.1版本为例。
以下是模块文件Mudule1.bas应该包含的内容:
Public Declare Sub CopyMemory Lib"kernel32.dll"Alias" RtlMoveMemory"(Destination As Any,Source As Any,ByVal Length As Long)
该Win32 API用于后面的lua_tostring函数。
在模块中还用到了lua.h头文件中定义的几个常量。例如:
#define LUA_GLOBALSINDEX(-10002)
在VB中转换为:
Public Const LUA_GLOBALSINDEX As Long=(-10002)
在lua.h中找到LUA_MULTRET、LUA_TBOOLEAN、LUA_TNUMBER、LUA_TSTRING参照以上方式进行定义。
模块使用了一些Lua常用的API,如:
LUALIB_API void(luaL_openlibs)(lua_State*L);
在VB中转换为:
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,参照上述方式进行定义。注意函数返回类型除了lua_tonumber为Double外,其余均为Long;参数类型除了const char*应定义为String外,其余均定义为Long;参数调用方式为ByVal按值调用。
在lua.h还定义了一些宏,例如:
#define lua_getglobal(L,s)lua_getfield(L,LUA_GLOBALSINDEX,(s))
这些API在Lua.dll中无导出函数,VB需用函数来实现:
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编写相应函数。而lua_tostring处理相对较多,对应VB函数如下:
在lauxlib.h中还有一个要用的API,对应VB函数如下:
(2)VB使用的类模块
Lua提供的API很多,对于宿主程序来说,未必会使用到全部的功能,最核心的无非是调用Lua函数、Lua调用宿主函数、运行Lua语句块、访问Lua全局变量等交互。因此,为了方便进行处理,我们可以建立以下类模块。限于篇幅,类模块将处理的数据类型缩减为数值、字符串和布尔三种常用类型,实际应用时可根据需要进行增加。
下面是类模块文件Class1.cls的主要内容:
Dim L As Long'Lua虚拟机指针
Public Function LuaOpen()As Long'新建虚拟机,代码略
Public Function LuaClose()As Long'关闭虚拟机,代码略
Public Function LuaPushValue(ByVal value As String)As Long'根据参数字串内容压入数据数值、字串或布尔值
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'返回栈顶内容字串
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'取变量内容,代码略
Public Function LuaSetVar(ByVal varname As String,By-Val value As String)As Long'设置变量值,代码略
Public Function LuaDoString(ByVal s As String)As Long'执行字符串中的语句块,代码略
Public Function LuaDoFile(ByVal fn As String)As Long'执行文件,代码略
Public Function LuaCall(ByVal fun As String,ByVal para As String,ByVal nRes As Long)As String'调用Lua中的函数
Dim iErr As Long,nPara As Long,A ()As String,i As Long
lua_getglobal L,fun
A=Split(para,",")'参数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为结果个数
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函数,lp为VB函数指针
lua_pushstring L,fun
lua_pushcfunction L,lp
LuaReg=lua_settable(L,LUA_GLOBALSINDEX)
End Function
(3)实例
将Lua.dll复制到System32目录中,新建工程“标准EXE”,导入模块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
运行并点击Command1,可得到运行结果。
ASP、WSH、VBS等脚本语言不能直接使用DLL,我们可以将上述模块封装为COM组件,那么这些高级语言就可以通过COM使用Lua来进行扩展了。
在VB中新建工程“ActiveX DLL”,导入模块Mud-ule1.bas和类模块Class1.cls(命名为LuaClass),在菜单“工程”-“属性”处,更改工程名为“Lua”,点击“文件”-“生成dll”。假设生成c:LuaX.dll,运行regsvr32 c:luaX. dll,复制Lua.dl1到System32中,然后上述脚本语言就可以通过COM使用Lua了。如建立并运行如下脚本文件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软件厂商发布2014年7月份编程语言排行榜中,VB由2013年的第7位升高到第5位,占4.341%;而Lua排名36位,占0.332%。这说明即使在Microsoft转向.NET平台多年以后,VB依然受到编程者的喜爱,而Lua的发展潜力很大。在VB中调用Lua,结合VB的快速开发特性和Lua的灵活性,我们开发的应用将更有弹性,而对于Lua接口API的再封装,无疑为ASP等脚本语言使用Lua打开了一扇大门。
[1]Roberto Ierusalimschy.Lua程序设计[M].2版.周惟迪,译.北京:电子工业出版社,2008.
[2]邓正阳,陈和平,苏鹏.动态脚本语言Lua与C++交互方法的研究与实现[J].计算机系统应用,2010,19(5):198-201.
[3]魏江平.LUA脚本语言在游戏引擎中的应用分析[J].微型电脑应用,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]张帆.可重构软件平台构建原理与应用研究[D].武汉理工大学,2012.
[6]邓楠乔,秦开宇,金燕华.基于Lua的面向组件程序设计研究[J].中国高新技术企业,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脚本技术的网络化测控系统设计[J].西安邮电大学学报,2013,18(1):90-94.
[9]苗新亮,刘栋,雷波,杨远辉.Lua脚本语言在安全设备管理系统中的应用[J].信息安全与通信保密,2014,(2):92-94.
[10]孙秀梅,巩建华.Visual Basic开发实战1200例(第1卷)[M].北京:清华大学出版社,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-),男,广西桂林人,讲师,硕士,研究方向为计算机程序设计、远程教育技术
2015-09-15
2015-09-30
VB和Lua都是常用的编程语言,为了结合利用VB和Lua各自的优点,快速、高效、灵活地进行应用程序开发,通过对C与Lua之间交互模式的研究分析,提出VB与Lua交互调用的方法,并进一步将VB调用Lua的接口封装为COM组件,以隐藏细节信息和简化调用接口,可支持更多其他编程语言如ASP、VBS等调用Lua。实践证明,这种VB 与Lua的交互调用方式是有效的、易于使用的。
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.