找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 202|回复: 0

LUA整合进MFC代码

[复制链接]

210

主题

371

回帖

0

积分

管理员

积分
0
发表于 2013-10-23 20:52:18 | 显示全部楼层 |阅读模式
[bgcolor=#ffffff]这几天研究了一下lua,主要关注的是lua和vc之间的整合,把代码都写好放在VC宿主程序里,然后在lua里调用宿主程序的这些代码(或者叫接口、组件,随便你怎么叫),希望能用脚本来控制主程序的行为。这实际上也是一种把业务分离,用脚本控制的架构,可能有些人把这种脚本叫做业务引擎,工作流等。[/bgcolor]
[bgcolor=#ffffff]为什么选择lua?[/bgcolor]
[bgcolor=#ffffff]因为它是一个能和C/C++结合得很紧的脚本语言,而我们的程序是用VC++ 写的;另外一点是因为它的名气,连WOW都用lua来提供API让玩家修改其游戏行为,那我是找不到什么理由拒绝它了。[/bgcolor]
[bgcolor=#ffffff]Lua是什么?在哪里获取LUA?[/bgcolor]
[bgcolor=#ffffff]详细的不说了,在网上一搜大把,只说一下它的官网吧:[/bgcolor]www.lua.org[bgcolor=#ffffff],在这里可以查到lua的应用,lua发布的版本,我用的是5.1.4,下载的是源代码的版本。[/bgcolor]
[bgcolor=#ffffff]LUA和VC MFC的整合?[/bgcolor]
[bgcolor=#ffffff]1、 包含LUA:要使用LUA,当然要先把它包含进我们的工程里,可以有lib/dll方式、也可以用静态lib方式,当然也可以把整个lua的代码放进我们的工程,然后编译,因为lua只有几百K,很小。。。包含整个代码的方法我说一下: [/bgcolor]
[bgcolor=#ffffff]a) 在VC MFC新建一个工程(例如Dialog base工程) [/bgcolor]
[bgcolor=#ffffff]b) 去到工程里的文件tab页,新建一个文件夹,然后把所有lua里的.c、.h文件包含进来,注意有几个不用包含,lua.c、wmain.c、luac.c,包含进来之后,选中这个文件夹下面的所有.c文件,然后右键选setting,选择Not using precompiler file,[/bgcolor]
[bgcolor=#ffffff]在VS2008中,右键.CPP文件,[/bgcolor]
[bgcolor=#ffffff]--->属性---->C\C++----->预编译头----->创建/使用预编译头----->选择 :不使用预编译头[/bgcolor]

[bgcolor=#ffffff]这步做完就马上编译一下,应该是没问题的了! [/bgcolor]
[bgcolor=#ffffff]c) 还有动态库和静态lib两种方式把lua包含进工程里的,自己可以尝试一下。 [/bgcolor]
[bgcolor=#ffffff]2、 在某个地方(我是在MFCLua1Dlg.cpp文件开始处)包含lua的头文件,并声明一个lua_state指针,如下: [/bgcolor]
[bgcolor=#ffffff]extern "C"[/bgcolor]
[bgcolor=#ffffff]{[/bgcolor]
[bgcolor=#ffffff]#include "lua.h"[/bgcolor]
[bgcolor=#ffffff]#include "lualib.h"[/bgcolor]
[bgcolor=#ffffff]#include "lauxlib.h"[/bgcolor]
[bgcolor=#ffffff]}[/bgcolor]
[bgcolor=#ffffff]lua_State *lua;[/bgcolor]

[bgcolor=#ffffff]在某个适当的地方(我是在OnInitDialog里)调用下面一段代码,这段代码的作用是打开一些必要的库:[/bgcolor]
[bgcolor=#ffffff]       lua = lua_open ();  [/bgcolor]
[bgcolor=#ffffff]       if(lua)[/bgcolor]
[bgcolor=#ffffff]       {[/bgcolor]
[bgcolor=#ffffff]              luaopen_base (lua);[/bgcolor]
[bgcolor=#ffffff]              luaopen_table (lua);[/bgcolor]
[bgcolor=#ffffff]              luaopen_string (lua);[/bgcolor]
[bgcolor=#ffffff]              luaopen_math (lua);[/bgcolor]
[bgcolor=#ffffff]              luaopen_debug (lua);[/bgcolor]
[bgcolor=#ffffff]              //luaopen_io (lua);[/bgcolor]
[bgcolor=#ffffff]       }[/bgcolor]
[bgcolor=#ffffff]用完lua的时候,调用下面一句来关闭lua库:[/bgcolor]
[bgcolor=#ffffff]lua_close (lua);[/bgcolor]
[bgcolor=#ffffff]              [/bgcolor]
[bgcolor=#ffffff]好了,到现在为止,lua已经完全变成我们程序的一部分了,试着编译一下,看看能不能顺利通过。。。[/bgcolor]
[bgcolor=#ffffff]LUA和MFC的交互?[/bgcolor]
[bgcolor=#ffffff]Lua变成我们程序的一部分之后,我们还要使用它,要记住我们的目标是用脚本程序控制我们宿主程序的执行流程,那我们就要完成两步,一是用mfc程序调用lua的函数,二是用lua调用mfc的函数,下面的内容对于初学者可能会开始有点难理解了,请打醒十二分精神,我会尽量简单的说。。。[/bgcolor]
[bgcolor=#ffffff]1、 mfc调用lua的函数,这里用到一个StackDump的函数,是关于主程序和lua的交互栈的问题,下面会对交互栈的问题专门说明。[/bgcolor]
[bgcolor=#ffffff]首先我们用记事本建立一个test.lua,内容是一个相加函数:[/bgcolor]
[bgcolor=#ffffff]function add ( x, y ) [/bgcolor]
[bgcolor=#ffffff]return x + y;[/bgcolor]
[bgcolor=#ffffff]end[/bgcolor]
[bgcolor=#ffffff]然后再VC里调用它,如下的一段代码,看这段代码的时候,先把StackDump函数忽略,只需要知道它是一个输出lua和vc交互栈内容的函数,对了,你可以新建一个button的click函数,然后把这段代码放进去:[/bgcolor]
[bgcolor=#ffffff]StackDump(lua);    [/bgcolor]
[bgcolor=#ffffff]luaL_dofile(lua, "test.lua");     // 解释分析lua文件[/bgcolor]
[bgcolor=#ffffff]StackDump(lua);[/bgcolor]
[bgcolor=#ffffff]lua_getglobal(lua, "add");       // 取到一个全局标号add,取的同时会把add函数压栈[/bgcolor]
[bgcolor=#ffffff]StackDump(lua);    [/bgcolor]
[bgcolor=#ffffff]lua_pushnumber(lua, 1);        // 把第一个参数压入栈里[/bgcolor]
[bgcolor=#ffffff]StackDump(lua);[/bgcolor]
[bgcolor=#ffffff]lua_pushnumber(lua, 2);        // 第二个参数压栈[/bgcolor]
[bgcolor=#ffffff]StackDump(lua);[/bgcolor]
[bgcolor=#ffffff]//lua_call(lua, 2, 1); [/bgcolor]
[bgcolor=#ffffff]if(lua_pcall(lua, 2, 1, 0) != 0)        // 执行add函数[/bgcolor]
[bgcolor=#ffffff]{[/bgcolor]
[bgcolor=#ffffff]        AfxMessageBox("lua_pcall error!");             [/bgcolor]
[bgcolor=#ffffff]return;[/bgcolor]
[bgcolor=#ffffff]}[/bgcolor]
[bgcolor=#ffffff]StackDump(lua);[/bgcolor]
[bgcolor=#ffffff]int d = (int)lua_tonumber(lua, -1);        // 函数执行完了,执行结果被压栈,所以取得最顶端的一个数就是结果值,-1就是指取栈顶的值[/bgcolor]
[bgcolor=#ffffff]CString str;[/bgcolor]
[bgcolor=#ffffff]str.Format("%d", d);[/bgcolor]
[bgcolor=#ffffff]AfxMessageBox(str);[/bgcolor]
[bgcolor=#ffffff]StackDump(lua);[/bgcolor]
[bgcolor=#ffffff]lua_pop(lua, 1);      // 把值从栈里清除,pop(弹出)一个值[/bgcolor]
[bgcolor=#ffffff]StackDump(lua);[/bgcolor]
[bgcolor=#ffffff]好好分析一下这段代码,我们大概知道调用lua函数的一个过程是:dofile--〉函数名压栈--〉参数依序压栈--〉lua_pcall执行(执行结果压栈)--〉取出执行结果(如果有多个,就从栈里取出多个。。。),这样我们就能很轻松的调用到lua里的函数,其实就是要知道栈里发生了什么。。。[/bgcolor]
[bgcolor=#ffffff]2、 lua调用MFC函数,比如我们想在lua里调用一个Msg函数,能弹出一个窗口来显示我们想显示的字串,然后返回值是1个"MsgOK!"字串。[/bgcolor]
[bgcolor=#ffffff]lua文件是这样的,第一句是调用Msg函数,第二句是测试返回的字串是不是"MsgOK!":[/bgcolor]
[bgcolor=#ffffff]c = Msg ("123");[/bgcolor]
[bgcolor=#ffffff]Msg(c);[/bgcolor]
[bgcolor=#ffffff]MFC程序里是这样的:[/bgcolor]
[bgcolor=#ffffff]首先写一个将要被导出的函数,很多文章叫它为粘合函数(glue function):[/bgcolor]
[bgcolor=#ffffff]static int Msg(lua_State* L)[/bgcolor]
[bgcolor=#ffffff]{[/bgcolor]
[bgcolor=#ffffff]const char *s1 = luaL_checkstring(L, 1);     // 测试第一个参数是否为字串形式,并取得这个字串[/bgcolor]
[bgcolor=#ffffff]StackDump(L);[/bgcolor]
[bgcolor=#ffffff]MessageBox(NULL, s1, "caption", MB_OK);[/bgcolor]
[bgcolor=#ffffff]lua_pop(lua, 1);      // 清除栈里的这个字串[/bgcolor]
[bgcolor=#ffffff]StackDump(L);[/bgcolor]
[bgcolor=#ffffff]lua_pushlstring(L, "MsgOK!", 6);  // 把返回值压进栈里[/bgcolor]
[bgcolor=#ffffff]// 这个返回是指返回值的个数[/bgcolor]
[bgcolor=#ffffff]return 1;[/bgcolor]
[bgcolor=#ffffff]}[/bgcolor]
[bgcolor=#ffffff]       然后就导出这个函数,如下:[/bgcolor]
[bgcolor=#ffffff]       lua_pushcfunction(lua, Msg);[/bgcolor]
[bgcolor=#ffffff]       lua_setglobal(lua, "Msg");[/bgcolor]
[bgcolor=#ffffff]       接着就执行刚才的lua文件就行了,记得执行之前要先lua_open () 哦:[/bgcolor]
[bgcolor=#ffffff]       luaL_dofile(lua, "test.lua");[/bgcolor]
[bgcolor=#ffffff]       运行的结果就是连续跳出两个messagebox,第一个是123,第二个是"MsgOK!",说明我们返回的字串被lua接收到了,lua的第二行我们没有接收它的返回值,则这个返回值会自动被抛弃了。[/bgcolor]
[bgcolor=#ffffff]       如果需要多返回值,则我们要把下面一句:[/bgcolor]
[bgcolor=#ffffff]lua_pushlstring(L, "MsgOK!", 6);  // 把返回值压进栈里[/bgcolor]
[bgcolor=#ffffff]// 这个返回是指返回值的个数[/bgcolor]
[bgcolor=#ffffff]return 1;[/bgcolor]
[bgcolor=#ffffff]改为:[/bgcolor]
[bgcolor=#ffffff]lua_pushlstring(L, "MsgOK!", 6);  // 把返回值压进栈里[/bgcolor]
[bgcolor=#ffffff]lua_pushlstring(L, "haha!", 5);      // 把返回值压进栈里[/bgcolor]
[bgcolor=#ffffff]// 这个返回是指返回值的个数[/bgcolor]
[bgcolor=#ffffff]return 2;[/bgcolor]
[bgcolor=#ffffff]这样我们在lua文件里就可以像下面一样取得两个返回值了:[/bgcolor]
[bgcolor=#ffffff]c,d = Msg("123");[/bgcolor]
[bgcolor=#ffffff]那c和d就分别是"MsgOK!"和"haha!"两个字串了。 这种自动机制用起来还是比较方便的。[/bgcolor]
[bgcolor=#ffffff]3、交互栈[/bgcolor]
[bgcolor=#ffffff]上面两个调用其实都是对lua栈的实用,那我们就要好好理解一个概念,lua和vc的交互栈(栈是什么?请参考数据结构的书哈。。。)lua和vc就是通过这个栈来实现交互的,这个栈的访问函数有lua_gettop,lua_settop,lua_tostring,lua_toXXX等等的函数,我们要清楚当一个函数调用发生的时候,栈里是发生了什么。上面我用了一个StackDump函数,当我们调用的时候,能很清楚的看到栈里发生了什么。[/bgcolor]
[bgcolor=#ffffff]       首先我们要知道从栈顶往下数就是-1、-2,从栈底往上数就是1、2。[/bgcolor]
[bgcolor=#ffffff]如果使用lua_gettop(L, 1),就是取得栈底第一个元素。lua_gettop(L, -1)就是取得栈顶的第一个元素。lua_pop() (L, 1)就是把栈顶的一个元素弹出来,lua_pop()(L, 2)就是把栈顶的两个元素弹出。[/bgcolor]
[bgcolor=#ffffff]好了,写了一通,最后是这个StackDump函数的实现:[/bgcolor]
[bgcolor=#ffffff]int StackDump(lua_State* L)[/bgcolor]
[bgcolor=#ffffff]{[/bgcolor]
[bgcolor=#ffffff]       int nTop = lua_gettop(L); //得到栈的元素个数。栈顶的位置。[/bgcolor]
[bgcolor=#ffffff]       OutputDebugString("The Length of stack is %d\n", nTop); //输出栈顶位置[/bgcolor]
[bgcolor=#ffffff]       for (int i = 1; i <= nTop; ++i)[/bgcolor]
[bgcolor=#ffffff]       {[/bgcolor]
[bgcolor=#ffffff]              int t = lua_type(L, i);[/bgcolor]
[bgcolor=#ffffff]              OutputDebugString("%s:", lua_typename(L, t)); //这里的typename是把类型的枚举变成字符串,是类型名。不是栈中的位置。[/bgcolor]
[bgcolor=#ffffff]              switch(t)[/bgcolor]
[bgcolor=#ffffff]              {[/bgcolor]
[bgcolor=#ffffff]              case LUA_TNUMBER:[/bgcolor]
[bgcolor=#ffffff]                     OutputDebugString("%f", lua_tonumber(L, i));[/bgcolor]
[bgcolor=#ffffff]                     break;[/bgcolor]
[bgcolor=#ffffff]              case LUA_TSTRING:[/bgcolor]
[bgcolor=#ffffff]                     OutputDebugString("%s", lua_tostring(L, i));[/bgcolor]
[bgcolor=#ffffff]                     break;[/bgcolor]
[bgcolor=#ffffff]              case LUA_TTABLE:[/bgcolor]
[bgcolor=#ffffff]                     //OutputDebugString("%s\n", lua_tostring(L,i));[/bgcolor]
[bgcolor=#ffffff]                     break;[/bgcolor]
[bgcolor=#ffffff]              case LUA_TFUNCTION:[/bgcolor]
[bgcolor=#ffffff]                     //OutputDebugString("%s\n", lua_tostring(L,i));[/bgcolor]
[bgcolor=#ffffff]                     break;[/bgcolor]
[bgcolor=#ffffff]              case LUA_TNIL:[/bgcolor]
[bgcolor=#ffffff]                     OutputDebugString("Is NULL");[/bgcolor]
[bgcolor=#ffffff]                     break;[/bgcolor]
[bgcolor=#ffffff]              case LUA_TBOOLEAN:[/bgcolor]
[bgcolor=#ffffff]                     OutputDebugString("%s", lua_toboolean(L, i) ? "true" : "false");[/bgcolor]
[bgcolor=#ffffff]                     break;[/bgcolor]
[bgcolor=#ffffff]              default:[/bgcolor]
[bgcolor=#ffffff]                     break;[/bgcolor]
[bgcolor=#ffffff]              }[/bgcolor]
[bgcolor=#ffffff]              OutputDebugString("\n");[/bgcolor]
[bgcolor=#ffffff]       }[/bgcolor]
[bgcolor=#ffffff]       return 0;[/bgcolor]
[bgcolor=#ffffff]}[/bgcolor]
[bgcolor=#ffffff]本篇文章主要讲了lua和VC的整合、把LUA源代码和VC工程一起编译,VC调用LUA的代码,LUA调用VC的代码,返回值以及多个返回值、交互栈、输出交互栈里的元素信息等内容,下一篇将会说说如何避免阻塞的脚本,lua和多线程的使用等内容。[/bgcolor]
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

果子博客
扫码关注微信公众号

Archiver|手机版|小黑屋|风叶林

GMT+8, 2026-2-1 03:25 , Processed in 0.110596 second(s), 20 queries .

Powered by 风叶林

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表