Lua和C++语言的交互详解
前言
写过windows程序的人都知道,对于应用程序,如果需要在本地保存一些配置信息,我们经常将这些配置信息写在注册表或者本地的配置文件中,很多应用都是将一些配置信息写在配置文件中,比如以ini结尾的文件,这种配置文件很多,使用的很广泛,然后应用程序在启动的时候,就会解析这个配置文件,读取一些配置信息。
lua的一项重要用途就是作为一种配置语言。而这篇文章将结合lua来扩展应用程序,这种方式提供了更大的灵活性和便利性。
这篇博文主要总结的是使用c++和lua进行交互,涉及到获取lua中普通变量的值,lua中table的值和调用lua中的函数。下面就开始吧。
从一个最简单的例子开始
一个gui程序,从配置文件读取窗口的大小,从而实现设置窗口的大小。下面我就写一个基于mfc的窗体程序来完成这个功能。点击这里去下载完成代码工程。我把重点的代码贴出来:
bool cluaconfig::loadconfig()
{
l = lual_newstate();
if (!l)
{
return false;
}
// 加载配置文件
int bret = lual_loadfile(l, pconfigfile);
if (bret)
{
return false;
}
// 运行配置文件
bret = lua_pcall(l, 0, 0, 0);
if (bret)
{
return false;
}
// 读取高
lua_getglobal(l, "width");
lua_getglobal(l, "height");
// width
if (!lua_isnumber(l, -2))
{
return false;
}
// height
if (!lua_isnumber(l, -1))
{
return false;
}
iwindowheight = lua_tointeger(l, -1);
iwindowwidth = lua_tointeger(l, -2);
return true;
}
lual_newstate就不说了,用烂了;lual_loadfile用于加载一个lua文件,然后调用lua_pcall运行编译好的程序块,lua_pcall是在保护模式下运行lua代码,也就是说,出错了,lua_pcall会返回一个错误代码,并不会直接crash。当运行完程序块后,调用了两次lua_getglobal函数,这个函数会将全局变量值压入栈中,所以,width的值在索引为-2的位置,height的值在索引为-1的位置;接下来,就不用多说了。就是这样。下载程序,运行一下,就ok了,修改代码文件夹下的config.lua文件,看看运行结果。源代码这里下载。
table操作
在lua中,对于table这种bug一样存在的东西,如果c api无法操作table,那我们还能不能愉快的玩耍了。让我们来看看c api如何操作table。现在有如下lua语句:
background = {r = 0.3, g = 1, b = 0.5}
那么,c api如何读取这段代码,将其中的每个字段都解析出来呢。我先把代码贴上来,然后一句一句的分析:
// 读取全局的数据到栈中
lua_getglobal(l, "background");
if (!lua_istable(l, -1))
{
// 如果不是table,就显示错误信息
cout << "it's not a table." << endl;
return 0;
}
// 读取table中字段的值,将值压入栈中
lua_getfield(l, -1, "r");
// 读取栈中的值
if (!lua_isnumber(l, -1))
{
// 如果不是实数,就显示错误信息
cout << "it's not a number." << endl;
return 0;
}
double fvalue = lua_tonumber(l, -1);
cout << "r => " << fvalue << endl;
原谅我省略了lual_newstate这样的代码。好了,读取一个table,同读取一个全局的变量是一个道理的。分为以下几步:
1.使用lua_getglobal读取这个变量,将table读取到栈中;
2.使用lua_getfield读取table中字段的值,将字段的值读取到栈中;
3.使用lua_to*系列函数,将字段的值从栈中读取出来。
这是读取table的操作,那设置table的操作呢?我们可以将我们自己的值写入到栈中,这该怎么操作?看代码:
// 将需要设置的值设置到栈中
lua_pushnumber(l, 0.55);
// 将这个值设置到table中
lua_setfield(l, -2, "r");
就是上面两行代码,当然了,你也需要先使用lua_getglobal读取table变量,将table读取到栈中,然后按照上面的两行代码进行设置就ok了。上面两行代码的具体含义是什么呢?
1.lua_push*系列函数是将一个需要设置的新值放到栈中;
2.lua_setfield函数同lua_getfield是一个性质的函数,只不过这里是set语义,将lua_push*到栈中的值,设置到table对应的key中。
现在读取table,设置table都说了,那如何在表中完全创建一个新的table呢?我们有这种需求。怎么办?
// 创建一个新的table,并压入栈
lua_newtable(l);
// 往table中设置值
lua_pushstring(l, ""); // 先将值压入栈
lua_setfield(l, -2, "website"); // 将值设置到table中
// 再设置一个值
lua_pushstring(l, "果冻想 | 一个原创文章分享网站");
lua_setfield(l, -2, "description");
我将重要的几行代码贴上来了,最重要的就是一个lua_newtable函数,该函数会创建一个新的table,并将这个table置于栈中,接下来就和上面设置table的值是一样的。源代码下载一、下载二。
调用lua函数
是的,你没有看错,你可以在一lua文件中定义一个函数,然后在c++中调用这个函数,貌似“高大上”的感觉。现在我就来说说这个“高大上”的功能;习惯性的上代码:
// 再来看看有参数和返回值得函数调用
// 现在在test.lua中定义了一个add函数,计算两个值的和,这两个值就是用参数传进去的
// 得到和以后,会返回这个和,现在我们就在c++这边调用这个add函数
lua_getglobal(l, "add"); // 获取函数,压入栈中
lua_pushnumber(l, 10); // 压入第一个参数
lua_pushnumber(l, 20); // 压入第二个参数
// 完成调用
iret = lua_pcall(l, 2, 1, 0);
if (iret)
{
const char *perrormsg = lua_tostring(l, -1);
cout << perrormsg << endl;
lua_close(l);
return 0;
}
// 获得计算结果
iret = lua_isnumber(l, -1);
if (!iret)
{
cout << "error occured." << endl;
lua_close(l);
return 0;
}
double fvalue = lua_tonumber(l, -1);
cout << "result is " << fvalue << endl;
上面代码是调用以下lua函数:
-- 有参数,有返回值
function add(ia, ib)
return ia + ib
end
这个简单的lua函数没有任何讲的地方,说说上面的那一长段c++代码吧。在lua中,函数和普通的值是一样的,所以,c++调用lua中的函数,分为以下几步:
使用lua_getglobal来获取函数,然后将其压入栈;
如果这个函数有参数的话,就需要依次将函数的参数也压入栈;
这些准备工作都准备就绪以后,就调用lua_pcall开始调用函数了,调用完成以后,会将返回值压入栈中;
最后取返回值得过程不用多说了,调用完毕。
源代码这里下载。
总结
到此这篇文章总结完毕,总共花费4天的业余的零碎时间,时间主要花费在demo的编写上,好了,这篇文章献上,希望对大家有帮助。如果你觉的还不错,可以将这篇文章分享给更多的朋友。当然了,你也可以扫描页面右侧的二维码资助我写出更好的文章了,那定是极好的。