lua(8)-C API 2[C++与lua的交互]

来源:互联网 发布:spring源码视频 编辑:程序博客网 时间:2024/05/29 17:38

上一篇我们提到许多c的api,这一篇我们就来看看如何实现基本的C++与lua的交互。

(1)基础示例

首先我们打开VS,新建一个c++控制台程序lua1,在我电脑上,这个新建的c++项目路径是F:\VSProject\lua1。

然后在lua的安装目录下找到include和lib文件夹


将include和lib文件夹拷贝至新建的c++项目中,拷贝到和.sln解决方案文件同一目录


拷贝完毕后,在vs中右键解决方案,找到属性


在C/C++中的“附加包含目录”加上../include


在链接器中的“附加库目录”加上../lib


附加包含目录和附加库目录添加完毕后,就可以在程序中通过#include来加载lua的头文件了。

lua1.cpp:

// lua1.cpp : 定义控制台应用程序的入口点。// #include "stdafx.h"#include "iostream"#include "stdio.h"#include "string.h"//lua.h和lualib.h作为C代码运行,在C++文件中使用lua.h和lualib.h,需使用extern命令实现混合编程,否则会提示无法解析外部函数extern "C"{    #include "lua.h"    #include "lauxlib.h"    #include "lualib.h"}using namespace std;#pragma comment(lib,"lua5.1.lib") int _tmain(int argc, _TCHAR* argv[]){    char buff[256];    int error;    //创建一个lua状态机(虚拟栈),这个状态机没有包含预定义的lua库函数    lua_State* L = luaL_newstate();    //打开标准库,这样状态机可以使用lua库函数,可以解释lua脚本    luaL_openlibs(L);    char fileName[] = "F:/LuaFile/lua1.lua";    //加载lua文件,解释器将lua文件的内容读取出来,动态编译为代码块    luaL_loadfile(L,fileName);    //执行编译好的代码块,参数1为栈指针,参数2为传给待调用函数的参数数量,    //参数3为期望返回的结果的数量,参数4为错误处理函数的索引(这个索引为压入栈的函数索引,0表示没有错误处理函数)    int result = lua_pcall(L,0,LUA_MULTRET,0);    //如果运行没有错误,lua_pcall将返回0    if(!result)    {        printf_s("lua脚本运行成功\n");    }    lua_close(L);    return 0;}

lua1.cpp新建完毕后,我们在F:/LuaFile/路径下新建一个lua1.lua文件,然后简单的写上一句打印


运行程序,看看结果


  • 上面的程序使用了luaL_newstate、luaL_openlibs、luaL_loadfile、lua_pcall、lua_close这几个c api,其中luaL_newstate用于在宿主程序中创建lua的虚拟机(栈);
  • 刚创建好的虚拟机是不具备lua的库环境的,因此需要luaL_openlibs函数打开lua的标准库;
  • 虚拟机的环境初始化完毕后,我们使用luaL_loadfile把lua1.lua文件的内容读取出来,读取后将内容视作代码进行编译,如果编译成功,将编译后的代码块压入虚拟机中;
  • 压入虚拟机中的代码块是可以被执行的,因此我们通过lua_pcall来执行这些代码块,压入的代码块正是lua1.lua中的print("I am lua1"),因此控制台中输出"Iam lua1";
  • 执行结束后,关闭虚拟机,至此,一个简单的c api示例程序运行完毕。

 

(2)数据输入

我们修改一下main函数里面的内容

int _tmain(int argc, _TCHAR* argv[]){    char buff[256];    int loadError;    int callError;    //创建一个lua状态机(虚拟栈),这个状态机没有包含预定义的lua库函数    lua_State* L = luaL_newstate();    //打开标准库,这样状态机可以使用lua库函数,可以解释lua脚本    luaL_openlibs(L);    //使用fgets输入流输入数据,如果使用scanf_s会受到空格的影响    while(fgets(buff,sizeof(buff),stdin) != NULL)    {        //接收用户输入的数据,编译为程序块并压入栈中,如果没有错误,返回0;如果有错误,压入一行错误信息的字符串        loadError =luaL_loadbuffer(L,buff,strlen(buff),"line");        if(loadError)        {            //打印这条错误信息,同时将错误信息压桟            printf_s("%s\n",lua_tostring(L,-1));            //弹出这条错误信息,第二个参数是从栈顶弹出元素的个数            lua_pop(L,1);        }       //执行代码块,并将代码块弹出栈       callError = lua_pcall(L,0,0,0);       if(callError)       {           printf_s("%s\n",lua_tostring(L,-1));           lua_pop(L,1);       }    }    lua_close(L);    return 0;}


运行结果


  • 上述程序将每一行的输入都动态编译成了lua的代码块,再通过lua_pcall函数来执行这些代码块。

通过luaL_loadbuffer函数,上述代码还可以写成这样子。

int _tmain(int argc, _TCHAR* argv[]){    char buff[256];    int loadError;    int callError;     //c++长字符串在每行后面用"\"接续    string luaBuff = "print('hello world');" \                     "local a = 123;" \                     "print(type(a));" \                     "local b = 'ABC';" \                     "print(type(b));" \                     "function d(...)" \                     "local str = 'abc^%&(';" \                     "print(string.gsub(str,'%w',''));" \                     "end;" \                     "d();";     //创建一个lua状态机(虚拟栈),这个状态机没有包含预定义的lua库函数    lua_State* L = luaL_newstate();    //打开标准库,这样状态机可以使用lua库函数,可以解释lua脚本    luaL_openlibs(L);     //接收用户输入的数据,编译为程序块并压入栈中,如果没有错误,返回0;如果有错误,压入一行错误信息的字符串    loadError =luaL_loadbuffer(L,luaBuff.c_str(),strlen(luaBuff.c_str()),"line");    if(loadError)    {        //打印这条错误信息,同时将错误信息压桟        printf_s("%s\n",lua_tostring(L,-1));        //弹出这条错误信息,第二个参数是从栈顶弹出元素的个数        lua_pop(L,1);    }    //执行代码块,并将代码块弹出栈    callError = lua_pcall(L,0,0,0);    if(callError)    {        printf_s("%s\n",lua_tostring(L,-1));        lua_pop(L,1);    }    lua_close(L);    return 0;}

运行结果


  • 可以看到,luaBuff这个字符串的内容也被当成是lua代码块来执行了。

(3)C++与lua间的通信

上述的例子都是直接加在的一个代码块去执行它,如果c++代码中有函数fun1和fun2,lua的代码中有函数fun3和fun4,现在需要在fun1中调用fun3,在fun4中调用fun2,那么程序可以写成这样子。

lua1.cpp:

// lua1.cpp : 定义控制台应用程序的入口点。// #include "stdafx.h"#include "iostream"#include "stdio.h"#include "string.h"//lua.h和lualib.h作为C代码运行,在C++文件中使用lua.h和lualib.h,需使用extern命令实现混合编程,否则会提示无法解析外部函数extern "C"{    #include "lua.h"    #include "lauxlib.h"    #include "lualib.h"}using namespace std;#pragma comment(lib,"lua5.1.lib") void fun1(lua_State* L){    printf_s("I am fun1,I'll call fun3\n");    //在全局范围内获得"fun3"的这个元素,并将这个元素的内容压入栈中    lua_getglobal(L,"fun3");    int callError;    callError = lua_pcall(L,0,0,0);    if(callError)    {        printf_s("%s\n",lua_tostring(L,-1));        printf_s("call fun3 error.");        lua_pop(L,1);    }} int fun2(lua_State* L){    printf_s("I am fun2~~~~~~~~~\n");    //返回值的个数为0    return 0;} int _tmain(int argc, _TCHAR* argv[]){    char buff[256];    int error;    //创建一个lua状态机(虚拟栈),这个状态机没有包含预定义的lua库函数    lua_State* L = luaL_newstate();    //打开标准库,这样状态机可以使用lua库函数,可以解释lua脚本    luaL_openlibs(L);    char fileName[] = "F:/LuaFile/lua1.lua";    //加载lua文件,解释器将lua文件的内容读取出来,动态编译为代码块    int loadFileError = luaL_loadfile(L,fileName);    //打印错误信息    if(loadFileError)    {        printf_s("%s\n",lua_tostring(L,-1));        lua_pop(L,1);    }     //向栈中压入c++的fun2    lua_pushcfunction(L,fun2);    //在栈中给fun2命名为"fun2",这样其他地方就能根据"fun2"这个字段索引到fun2的内容    lua_setglobal(L,"fun2");     //执行栈中的代码块    int callError = lua_pcall(L,0,0,0);    if(callError)    {        printf_s("%s\n",lua_tostring(L,-1));        lua_pop(L,1);    }     fun1(L);    lua_close(L);    return 0;}


然后是lua1.lua:


执行结果


可以看到,c++中的fun1调用了lua中的fun3,而lua中的fun4调用了fun2,这样就完成了最基本的交互。需要注意的是,lua_setglobal和lua_getglobal等方法需要在代码块被执行后(lua_pcall)才能生效。也就是说,如果只是把代码块加载进了栈中,但是不执行这些代码块,那么是获取不到栈中的这些全局变量的。

 

(4)函数的传参、返回值

我们对(3)中的lua1.cpp修改一下

lua1.cpp:

// lua1.cpp : 定义控制台应用程序的入口点。// #include "stdafx.h"#include "iostream"#include "stdio.h"#include "string.h"//lua.h和lualib.h作为C代码运行,在C++文件中使用lua.h和lualib.h,需使用extern命令实现混合编程,否则会提示无法解析外部函数extern "C"{    #include "lua.h"    #include "lauxlib.h"    #include "lualib.h"}using namespace std;#pragma comment(lib,"lua5.1.lib") void fun1(lua_State* L){    printf_s("I am fun1,I'll call fun3\n");    //在全局范围内获得"fun3"的这个元素,并将这个元素的内容压入栈中    lua_getglobal(L,"fun3");    //给fun3函数传参    lua_pushnumber(L,2);    lua_pushnumber(L,5);    int callError;    //由于有fun3有两个参数,同时有一个返回值,因此lua_pcall写成lua_pcall(L,2,1,0)    //lua_pcall会执行fun3方法,同时把fun3和处于栈顶的两个参数一起弹出栈,然后将fun3的返回值压桟    callError = lua_pcall(L,2,1,0);    if(callError)    {        printf_s("%s\n",lua_tostring(L,-1));        printf_s("call fun3 error.");        lua_pop(L,1);    }    else    {        //读出fun3的返回值,在栈顶        int returnNum = lua_tonumber(L,-1);        printf_s("fun3 return num:%d\n",returnNum);    }} int fun2(lua_State* L){    printf_s("I am fun2~~~~~~~~~\n");     //获得当前栈中被调用的函数的第一个参数,也就是"the param from fun4"    const char* paramStr = luaL_checkstring(L,1);    printf_s("I get the str:");    printf_s(paramStr);    printf_s("\n");     //把这个参数弹出栈    lua_pop(L,1);    //把返回值压入栈中    lua_pushstring(L,"fun2 getted the param.");     //返回值的个数为1    return 1;} int _tmain(int argc, _TCHAR* argv[]){    char buff[256];    int error;    //创建一个lua状态机(虚拟栈),这个状态机没有包含预定义的lua库函数    lua_State* L = luaL_newstate();    //打开标准库,这样状态机可以使用lua库函数,可以解释lua脚本    luaL_openlibs(L);    char fileName[] = "F:/LuaFile/lua1.lua";    //加载lua文件,解释器将lua文件的内容读取出来,动态编译为代码块    int loadFileError = luaL_loadfile(L,fileName);    //打印错误信息    if(loadFileError)    {        printf_s("%s\n",lua_tostring(L,-1));        lua_pop(L,1);    }     //向栈中压入c++的fun2    lua_pushcfunction(L,fun2);    //在栈中给fun2命名为"fun2",这样其他地方就能根据"fun2"这个字段索引到fun2的内容    lua_setglobal(L,"fun2");     //执行栈中的代码块    int callError = lua_pcall(L,0,0,0);    if(callError)    {        printf_s("%s\n",lua_tostring(L,-1));        lua_pop(L,1);    }     fun1(L);    lua_close(L);    return 0;}


然后修改一下lua1.lua的内容


执行程序,看看结果


可以看到c++的代码和lua的代码已经实现了相互的传参和获取返回值。

除了上述这些交互部分,还有许多种常见的数据交互,如c++中类、lua中的table等,这些部分我们在后面的篇章中再进行叙述,这篇文章就先这样了,嗯 = =。

 

0 0