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

上一篇我们提到许多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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值