LUA对于许多人,第一次接触到它可能是一些网络游戏,例如WOW,剑网等的游戏辅助工具,简单了解后,发现这是一种“低成本”的快速为你的软件(或游戏)加入与第三方交互能力(例如与另一个软件共享数据)的开发工具;
——LUA,C,SDK,软件交互,API,第三方
V1.0 By meineson 2012/01
LUA对于许多人,第一次接触到它可能是一些网络游戏,例如WOW,剑网等的游戏辅助工具,简单了解后,发现这是一种“低成本”的快速为你的软件(或游戏)加入与第三方交互能力(例如与另一个软件共享数据)的开发工具;tp://
继续深入了解,发现LUA已经在我们日常使用的软件里广泛使用,应用的方式也五花八门,按“官方”一些的说法,LUA最初是用于实现一个强大的软件“配置文件”,相比INI和XML是纯“静态”的数据,LUA的脚本程序出身,其编程能力带来的无疑是更强大的能力,而且它从设计之初就本着快速、简洁的“寄生”脚本程序原则,相比Python、PHP,没有整合太多的平台依赖的本地功能库(只提供基本的字符串,标准IO等功能库),以ANSI C为基础开发,使得其非常小巧,非常容易整合进从嵌入式到服务器平台的各种以类C语言开发环境中去;
PS:当然,由于LUA是开源的,如果你愿意,你也可以使用例如luasocket, luasql, luacom等扩展库,为LUA增加各种常用功能库;
依据LUA的功能特性,除了上述提到的,用于作为功能强大的软件配置文件使用,它也非常适合于分离软件的界面与功能逻辑,例如迅雷的新版界面,就是由许多的LUA脚本来驱动的,其基本原则是,利用LUA的寄生能力,与宿主程序间互相暴露功能函数,用C将执行具体的界面UI渲染显示功能的函数暴露给LUA脚本,使用LUA脚本实现界面逻辑的具体步骤实现;
这非常象网页浏览器,浏览器软件负责解析HTML和Javascript文本,并显示具体的文本图片,并接受JS脚本的控制调整显示的内容,这里,整合了LUA解析器的宿主C程序就象浏览器,而纯文本的LUA脚本就相当于HTML+Javascript网页,仅凭这一点,目前比较流行的DirectUI软件界面开发解决方案就可以借助LUA实现(至少是脚本支持可以借力LUA,比整合Javascript引擎要轻量的多);
我们这里不讨论DirectUI,如果有兴趣,可以查看本博客里另一篇文章介绍的开源的DirectUI实现,这个方案正好缺脚本支持,可以将LUA整合进去;
另外,由于LUA的免编译的脚本特性,也特别适合编写小的功能软件或测试软件,使用基本的文件或标准输入输出及字符串库,就可以很好地实现一些以往要用C或C++编写的测试代码段,而且非常易读易维护,特别是如果整合luasocket库(包含HTTP,FTP,SMTP等常用协议封装)、luacom库等,基本可以满足大多数平台上的软件功能代码块的测试需求;
上面说描述的这些,都是在LUA产生之前,已经有许多替代方案“可以”实现的,或者说,是由于历史原因,不允许替代的,而下面要描述的场景,则是如果不使用LUA,你需要付出成倍的努力,但可能都实现不太好,这就是为你的软件(特别是功能已经基本开发完毕的软件)提供与第三方交互的能力——例如你需要为你的软件增加插件支持能力,增加自动化(宏)编辑能力,交互示命令行支持等,这些都需要你了解甚至精通编程语言语法词法解析、编译原理、进程间通讯或网络通讯等能力;
而有了LUA,参考如下的简单几行代码,你的软件立即就有了支持高级编程语言语法解析、编译的第三方交互能力,其它第三方软件开发者就可以使用简洁的LUA语法直接为你的软件编写插件、功能模块、自动化宏支持等等;
** 我们下面要用到的,是标准的LUA的C API接口,没有使用到LuaJIT,LuaPlus等LUA的二次封装库,相关语法问题,请直接请教LUA官方手册及C语言开发文档;
LUA脚本内容:
(其中mb_clib是C程序导出的库,c_f1是库其中一个功能函数,接受一个输入参数,返回一个结果,不同action执行了相同功能函数仅作演示,实际工程中是执行不同具体功能)
function mb_plugin_main(action)
local ret = “”;
if action == “dial” then
ret = mb_clib.c_f1(action);
elseif action == “hold” then
ret = mb_clib.c_f1(action);
elseif action == “hang” then
ret = mb_clib.c_f1(action);
else
ret = “error paramter..”;
end
return ret;
end
C程序中(以VC2005示例,Win32工程示例):
- 配置工程环境
下载lua头文件及库:http://luabinaries.sourceforge.net/download.html ,例如lua5_1_4_Win32_dll8_lib.zip
解压将include下所有头文件加入工程,将dll, lib文件加入工程的合适的目录下;
在头文件中加入:
#pragma comment(lib, “lua51.lib”)
#include “lua.hpp”
在合适的位置,例如WinMain入口处,加入初始化代码:
lua_State *L = luaL_newstate();
luaL_openlibs(L);
记住在合适的地方要关闭lua库:
lua_close(L);
编译运行,如果上述lua API正确执行则表示环境已准备好,可以继续;
- 向LUA暴露C函数
向LUA暴露C函数的方法有两个,一个是直接暴露函数名,还有一个是以库的方式,我们这里选择用库的方式,以免代码组织和函数名使用上过于混乱;
声明函数,这些函数的定义格式是固定的,即输入参数是lua_State,输出结果是整形的返回值的数量,详细请查看lua的C API文档;
int c2lua_f1(lua_State *L)
{
MessageBoxA(NULL, lua_tostring(L, -1), "", MB_OK); //读取lua调用本函数时的输入参数并弹出对话框显示
MessageBoxA(NULL, "alert msg in c app", "", MB_OK); //函数功能主体,这里弹出一个对话框
lua_pushstring(L, "string result from c app function!"); //返回结果
return 1; //表示只有一个返回结果,即上述字符串
}
声明导出库:
static const struct luaL_Reg lua_lib[] =
{
{"c_f1", c2lua_f1}, //表示lua中c_f1函数对应C函数c2lua_f1
{NULL, NULL}
};
导出库:
luaL_register(L, “mb_clib”, lua_lib); //表示向lua导出库,名叫mb_clib,对应上述定义的注册库lub_lib
- 加载执行lua脚本:
luaL_loadfile(L, “plugin.lua”); //加载到内存里,并未编译执行,即,相当于浏览器把HTML代码下载到本地
lua_pcall(L, 0, 0, 0); //编译执行lua脚本
这是的执行是全局的脚本执行,即,执行脚本本身类似你直接在往lua的交互式命令行输入一段命令得到结果,此时LUA相当于一段批处理,执行完即结束,为了简化,可以把上述加载与这里执行合并为luaL_dofile(L, “plugin.lua”);
但在我们这个应用场景里很关键,我们需要的是一种插件或软件第三方交互能力,即,C程序向外暴露功能接口,在外部LUA中编写一些逻辑代码合理使用这些暴露的功能接口再次形成上层功能接口,然后再由C调用这些LUA中的上层功能接口完成一系列动作,即,我们这里,lua脚本被pcall执行后并未实现具体的功能,仅仅是编译;
但这个编译作了哪些工作,以及在什么时候编译这些脚本是一个关键概念,就象PHP动态在输出的HTML里生成javascript,这些js是什么时候执行很关键一样,C程序加载lua脚本并向lua中动态导出函数,在实际操作中的步骤实际是——向lua动态导出函数,再加载lua脚本,即loadfile或dofile一定要在register之后,lua在编译时从C API的动态注册数据中找新增的功能函数的;
- 执行“回调”——交互的核心
完成上述lua的编译执行工作后,我们才得到了lua的运行时环境,即,我们可以在C程序中调用外部编写的lua函数(而这些lua函数又调用了C暴露给lua的函数)代码是:
lua_getglobal(L, “mb_plugin_main”); //lua中的函数名
lua_pushstring(L, “dial”); //调用lua中函数要提供的参数
if(lua_pcall(L, 1, 1, 0) == 0) //具体执行,参数2,3分别是输入与输出的参数数量
{
MessageBoxA(NULL, lua_tostring(L, -1), NULL, MB_OK); //打印lua函数的输出值
lua_pop(L, 1);
}
有了上述代码基础,如要实用,我们需要扩展一下:
为lua_lib导出库注册更多的C程序中实际的功能函数;
定义规范外部插件入口函数mb_plugin_main的输入输出参数定义(lua支持可变数量的参数),或者也可以自行更多的外部插件入口函数;
则我们可以实现,C软件启动时扫描插件文件并挂载(或可配置使用不同或同时使用多个插件),在执行原程序内指定操作时——包括界面UI交互,内部程序事件等任意位置,将原先在C程序内要经常维护不停编译发新版本的工作,变为升级外部LUA脚本即可替代实现,或出厂时调用接口为空操作,可由客户或第三方编写扩展程序,实现与第三方软件“事件级”的底层交互,大大提升软件的协同工作能力;