上一篇我们提到许多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等,这些部分我们在后面的篇章中再进行叙述,这篇文章就先这样了,嗯 = =。