Lua学习笔记--C调用Lua

Lua是一种嵌入式语言,可以很好的嵌入其他应用程序。lua为我们提供了一组灵活的C API,使C代码能够很好的与Lua进行交互。包括读写Lua全局变量,调用Lua函数,运行Lua代码,注册C函数反过来供Lua调用。简单的说,C能调用Lua,反过来Lua也能调用C。真的是灰常强大灵活的脚本!!现在,先来学习一下怎么用C调用Lua。

其实最简单的我们已经做过了,通过一个dofile,运行一个lua脚本文件。

一.栈

Lua与C的交互是通过一个虚拟栈进行的,这个栈对于Lua来说是严格的LIFO(后进先出)的,当调用Lua时,Lua只会改变栈的顶部。不过C有更大的自由度,可以检索栈中元素,甚至在任意位置插入和删除元素。

当Lua启动或者Lua调用C语言时,栈中至少有20个空间的空闲槽,一般调用来说这些空间足够了。如果调用的参数特别特别多,需要先检查槽够不够用,使用下面的函数:

<span style="white-space:pre">	</span>int lua_checkstack(lua_State* L, int sz );
API使用索引来引用栈中的元素,记住最开始的索引为1,不是0!即第一个压入栈中的元素索引为1,第二个压入栈中的元素索引为2,直到栈顶。也可以使用负数的索引来访问栈顶的元素,即-1表示栈顶元素,以此类推。

在C语言的lua库中,提供了几个关于栈中元素操作的函数,由于C语言实现里没有泛型,所以,对应每一种数据类型都提供了一个函数,这里后面的数据类型暂时用*代替。

//检查栈中index索引的数据类型是否是*的类型
int lua_is*(lua_State * L, int index)
//返回栈中index索引的数据的类型
int lua_type(lua_State* L, int index)
//返回栈中index索引的数据的值,转化为*的类型
*   lua_to*(lua_State* L, int index)
//向栈中插入*类型的元素
void lua_push*(lua_State* L, type*)


既然这个东东是个栈,所以当然也提供了一些列栈本身的操作:

//获得栈中元素个数
int lua_gettop(lua_gettop) (lua_State *L);
//设置栈顶为一个指定位置
void lua_settop(lua_settop) (lua_State *L, int idx);
//将指定索引上的值再次压入栈
void lua_pushvalue(lua_State *L, int idx);
//删除指定索引的元素,之上的向下移补缺
void lua_remove(lua_State* L, int idx);
//在index处开辟一个位置,上面的上移,然后将栈顶元素放到这个位置
void lua_insert(lua_State* L, int idx);
//弹出栈顶元素,使用该元素替代index元素
void lua_replace(lua_State* L, int idx);


看一个例子,介个例子里面木有使用lua,直接使用的C语言操作这个栈,并查看其中内容:

// LuaTest.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

//因为Lua是C的函数,而我们的程序是C++的,所以要使用extern "C"引入头文件
extern "C"{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "luaconf.h"
}

//注意还需要添加之前编译好的Lualib.lib文件,这里通过项目->属性->连接器->附加依赖项添加了
//否则需要 #pragma comment(lib, "lualib.lib")来添加



//打印stack中的数据
void CheckStack(lua_State* L)
{
	int top = lua_gettop(L);//stack的大小
	
	//遍历stack所有层
	for (int i = 1; i <= top; i++)
	{
		int type = lua_type(L, i);
		switch (type)
		{
		case LUA_TSTRING://字符串
			printf("%s\n", lua_tostring(L, i));
			break;
		case LUA_TBOOLEAN://布尔值
			printf(lua_toboolean(L, i) ? "true\n" : "false\n");
			break;
		case LUA_TNUMBER://数字
			printf("%d\n", lua_tonumber(L, i));
			break;
		default://其他值
			printf("%s\n", lua_typename(L, i));
			break;
		}
	}
	printf("\n");
}




int _tmain(int argc, _TCHAR* argv[])
{
	//打开lua
	lua_State* L = luaL_newstate();
	//加载lib文件
	luaL_openlibs(L);
	
	//向栈中压入内容
	lua_pushboolean(L, 1);
	lua_pushstring(L, "hehe");
	lua_pushnumber(L, 100);
	
	//打印栈中内容
	CheckStack(L);

	//将index为1的内容再次压入栈中
	lua_pushvalue(L, 1);
	CheckStack(L);

	//删除index为2的元素
	lua_remove(L, 2);
	CheckStack(L);

	//设置栈顶为16(这个空了的地方貌似被补成每个类型一种,其余为空了)
	lua_settop(L, 16);
	CheckStack(L);

	//结束
	lua_close(L);
 
	system("pause");

	return 0;
}
结果:

true
hehe
0


true
hehe
0
true


true
0
true


true
0
true
string
table
function
userdata
thread
proto
(null)
(null)
(null)
(null)
(null)
(null)
(null)


请按任意键继续. . .

二.简单的调用Lua全局变量(可以作为配置文件)

既然lua可以被C/C++的程序加载,直接加载程序,并将其作为配置文件也是一个好的用处。
一个简单的例子:

lua文件:
--配置文件,包含两个全局变量
arg1 = 1
arg2 = 2

C++程序:
// LuaTest.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

//因为Lua是C的函数,而我们的程序是C++的,所以要使用extern "C"引入头文件
extern "C"{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "luaconf.h"
}

//注意还需要添加之前编译好的Lualib.lib文件,这里通过项目->属性->连接器->附加依赖项添加了
//否则需要 #pragma comment(lib, "lualib.lib")来添加



//打印stack中的数据
void CheckStack(lua_State* L)
{
	int top = lua_gettop(L);//stack的大小
	
	//遍历stack所有层
	for (int i = 1; i <= top; i++)
	{
		int type = lua_type(L, i);
		switch (type)
		{
		case LUA_TSTRING://字符串
			printf("%s\n", lua_tostring(L, i));
			break;
		case LUA_TBOOLEAN://布尔值
			printf(lua_toboolean(L, i) ? "true\n" : "false\n");
			break;
		case LUA_TNUMBER://数字
			printf("%d\n", lua_tonumber(L, i));
			break;
		default://其他值
			printf("%s\n", lua_typename(L, i));
			break;
		}
	}
	printf("\n");
}


//读取lua脚本文件的函数(此处作为一个配置文件)
void LoadLua(lua_State* L , const char* filename, int * arg1, int * arg2)
{
	if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))
		printf("loadfile failed! %s\n", lua_tostring(L, -1));//如果失败,栈顶为错误信息
	//获得全局变量,压入栈中
	lua_getglobal(L, "arg1");
	lua_getglobal(L, "arg2");
	//判断一下素不素想要的类型
	if (!lua_isnumber(L, -2))
		printf("arg1 is not number\n");
	if (!lua_isnumber(L, -1))
		printf("arg2 is not number\n");
	//提取栈中的参数值
	*arg1 = lua_tointeger(L, -2);
	*arg2 = lua_tointeger(L, -1);
}



int _tmain(int argc, _TCHAR* argv[])
{
	//打开lua
	lua_State* L = luaL_newstate();
	//加载lib文件
	luaL_openlibs(L);
	
	int i, j = 0;
	LoadLua(L, "test.lua", &i, &j);
	printf("i = %d, j = %d\n", i , j);

	//结束
	lua_close(L);
 
	system("pause");

	return 0;
}

结果:
i = 1, j = 2
请按任意键继续. . .


简单解释一下:

luaL_loadfile是加载lua文件,作为一个程序块,但是并不执行。

lua_pcall运行编译好的程序块。

lua_getglobal通过名称获得全局变量的值,压入栈中。


这样就通过C程序加载lua文件,达到了加载配置文件的目的,虽然看起来比较麻烦,不过封装一下的话,还是很好用的。而且使用Lua作为配置文件,一方面是容易操作,不需要额外写一些配置文件读取的工具,另一方面是可以在配置文件中添加注释等等,甚至还可以写一些条件判断等等。


三.读取table中的信息

 table类似于结构体,可以更加好的保存一些信息,当我们要存储的配置文件内容较多,且杂乱时,将其分组就是很好的选择。Lua支持C读取table中的信息。
下面看一个例子:

lua文件:
--配置文件,包含两个全局变量
struct = {arg1 = 1, arg2 = 2}

C++程序:
// LuaTest.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

//因为Lua是C的函数,而我们的程序是C++的,所以要使用extern "C"引入头文件
extern "C"{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "luaconf.h"
}

//注意还需要添加之前编译好的Lualib.lib文件,这里通过项目->属性->连接器->附加依赖项添加了
//否则需要 #pragma comment(lib, "lualib.lib")来添加



//打印stack中的大小
void CheckStack(lua_State* L)
{
	int top = lua_gettop(L);//stack的大小
	printf("The size of the stack is %d\n", top);
}


//读取一个table的数据,table更加结构化
void LoadLua(lua_State* L , const char* filename, int * arg1, int * arg2)
{
	//和直接读取一样,先加载file,然后执行
	if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))
		printf("loadfile failed! %s\n", lua_tostring(L, -1));//如果失败,栈顶为错误信息
	CheckStack(L);//此时stack为空

	//将struct结构体放入栈中
	lua_getglobal(L, "struct");
	if (! lua_istable(L, -1))
		printf("struct is not table!\n");
	CheckStack(L);//此时栈大小为1

	//从struct中提取arg1放在栈顶
	lua_getfield(L, -1, "arg1");
	if (! lua_isnumber(L, -1))
		printf("arg1 is not number!\n");
	CheckStack(L);//此时栈大小为2
	*arg1 = lua_tointeger(L, -1);

	//上一个元素用完了,就把栈顶元素弹出
	lua_pop(L, 1);
	CheckStack(L);//此时栈大小为1

	//提取arg2放在栈顶
	lua_getfield(L, -1, "arg2");
	if (! lua_isnumber(L, -1))
		printf("arg2 is not number!\n");
	CheckStack(L);
	*arg2 = lua_tointeger(L, -1);
}



int _tmain(int argc, _TCHAR* argv[])
{
	//打开lua
	lua_State* L = luaL_newstate();
	//加载lib文件
	luaL_openlibs(L);
	
	int i, j = 0;
	LoadLua(L, "test.lua", &i, &j);
	printf("i = %d, j = %d\n", i , j);

	//结束
	lua_close(L);
 
	system("pause");

	return 0;
}
结果:
The size of the stack is 0
The size of the stack is 1
The size of the stack is 2
The size of the stack is 1
The size of the stack is 2
i = 1, j = 2
请按任意键继续. . .


还是上次的那两个数据,不过这次他们被放在了一个table里面,即使有相同的N组,也可以用N个table来存储,不用担心杂乱的问题。

简单分析一下C读取table的过程:

还是通过loadfile加载进来,然后仍然是getglobal获得全局变量,但是这次的全局变量是一个table,在进行下一步操作之前先检查一下是否真是个table,然后,通过另一个函数getfiled()获得table中特定字段的内容,放在栈顶。读取一个之后,将其弹出,然后再次获得下一个字段的内容,读取,以此类推。


赶脚这个table类型的配置文件就跟XML差不多了。一直没找到C++下的好的XML解析器,实在不行以后就用Lua把。


四.调用Lua函数



终于到了这一步了,之前的仅仅能叫做配置文件,这一步才真正称得上是脚本!!
其实使用C调用Lua脚本比较简单,还是使用那个栈进行参数的传递。

看一个简单的例子:

Lua文件:
--add fucntion

function add(a, b)
	return a + b
end

C++程序:
// LuaTest.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

//因为Lua是C的函数,而我们的程序是C++的,所以要使用extern "C"引入头文件
extern "C"{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "luaconf.h"
}

//注意还需要添加之前编译好的Lualib.lib文件,这里通过项目->属性->连接器->附加依赖项添加了
//否则需要 #pragma comment(lib, "lualib.lib")来添加



//打印stack中的数据
void CheckStack(lua_State* L)
{
	int top = lua_gettop(L);//stack的大小
	
	//遍历stack所有层
	for (int i = 1; i <= top; i++)
	{
		int type = lua_type(L, i);
		switch (type)
		{
		case LUA_TSTRING://字符串
			printf("%s ", lua_tostring(L, i));
			break;
		case LUA_TBOOLEAN://布尔值
			printf(lua_toboolean(L, i) ? "true " : "false ");
			break;
		case LUA_TNUMBER://数字
			printf("%g ", lua_tonumber(L, i));
			break;
		default://其他值
			printf("%s ", lua_typename(L, i));
			break;
		}
	}
	printf("\n");
	printf("The size of the stack is %d\n", top);
}


//读取一个table的数据,table更加结构化
void LoadLua(lua_State* L , const char* filename, int * arg1, int * arg2)
{
	//和直接读取一样,先加载file,然后执行
	if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))
		printf("loadfile failed! %s\n", lua_tostring(L, -1));//如果失败,栈顶为错误信息
	CheckStack(L);//此时stack为空

	//将struct结构体放入栈中
	lua_getglobal(L, "struct");
	if (! lua_istable(L, -1))
		printf("struct is not table!\n");
	CheckStack(L);//此时栈大小为1

	//从struct中提取arg1放在栈顶
	lua_getfield(L, -1, "arg1");
	if (! lua_isnumber(L, -1))
		printf("arg1 is not number!\n");
	CheckStack(L);//此时栈大小为2
	*arg1 = lua_tointeger(L, -1);

	//上一个元素用完了,就把栈顶元素弹出
	lua_pop(L, 1);
	CheckStack(L);//此时栈大小为1

	//提取arg2放在栈顶
	lua_getfield(L, -1, "arg2");
	if (! lua_isnumber(L, -1))
		printf("arg2 is not number!\n");
	CheckStack(L);
	*arg2 = lua_tointeger(L, -1);
}




//调用Lua的函数
double add(lua_State* L , double x, double y)
{
	//压入函数和参数
	//提取lua中的函数,放入栈中
	lua_getglobal(L, "add");
	//将两个参数压入栈中
	lua_pushnumber(L, x);
	lua_pushnumber(L, y);
	CheckStack(L);

	//进行调用
	if (lua_pcall(L, 2, 1, 0) != 0)
		printf("function called failed! %s\n", lua_tostring(L, -1));
	CheckStack(L);

	//从栈中提取结果
	if (!lua_isnumber(L, -1))
		printf("function must return a double number!\n");

	double result = lua_tonumber(L, -1);

	lua_pop(L, 1);
	CheckStack(L);

	return result;
}


//读取lua文件并执行函数
void GetLuaFunctiorn(lua_State* L, const char* filename)
{
	if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))
		printf("load file failed! %s\n", lua_tostring(L, -1));

	double resulet = add(L, 1.5, 2.5);
	printf("result is %f\n", resulet);
}

int _tmain(int argc, _TCHAR* argv[])
{
	//打开lua
	lua_State* L = luaL_newstate();
	//加载lib文件
	luaL_openlibs(L);
	
	//使用函数
	GetLuaFunctiorn(L, "test.lua");

	//结束
	lua_close(L);
 
	system("pause");

	return 0;
}

结果:
boolean 1.5 2.5
The size of the stack is 3
4
The size of the stack is 1


The size of the stack is 0
result is 4.000000
请按任意键继续. . .


我们要调用Lua函数时,首先需要知道Lua中函数的名称以及参数个数。这些都需要在调用前放入栈中。函数需要通过getglobal函数根据名称提取,将函数放入栈中。然后将参数依次压入栈中。准备工作做好之后,就要通过lua_pcall函数进行Lua函数调用了。lua_pcall函数一共有4个参数,第一个参数为lua_State,第二个参数为传给函数的参数的数量,第三个参数为期望返回的结果的数量,最后一个参数为一个错误处理的函数的索引。与之前的赋值等操作一样,如果我们实际的值比lua_pcall给的值多,会舍去,如果少的话,使用nil补齐。
如果函数有多个返回参数的话,第一个参数先压入栈中,然后是第二个,以此类推。这时,比如返回了三个结果,那么第一个结果的索引为-3,第二个为-2,最后一个为-1。
如果lua函数运行出错的话,lua_pcall会返回一个非零的值,并且在栈中压入一条错误信息。但是在压入错误信息前,如果有错误处理函数,会先执行错误处理函数。lua_pcall的最后一个参数可以指定错误处理函数的位置,如果为0表明木有错误处理函数。如果有的话,这个参数表示错误处理函数在栈中的索引,所以如果有错误处理函数的话,就需要先把其压入栈中。
错误处理函数也不是什么错误都可以处理的,当出现内存分配错误时或者在错误处理函数出错时,都不会调用错误处理函数。











你可能感兴趣的:(c,脚本,lua,交互,函数调用)