C++调用lua 运行方式、案例亲测 及 常用API

2022-05-06 14:34:44 浏览数 (1)

文章目录
  • 运行方式
  • 常用API
    • 压入元素
    • 类型相关
    • 查询相关
    • 执行lua代码
    • 获取 Lua 代码执行结果
    • 其他常用API
  • 案例亲测

我也琢磨了好一会儿,这篇文章改了又改,主要是每一part的顺序问题。

运行方式

先看张图:

它的执行方式,有点熟悉,但是一时又实在想不起来那个案例。

波兰式,对,思考一下波兰式。

在Lua和C语言之间进行数据交换时,由于两种语言之间有着较大的差异,比如Lua是动态类型,C语言是静态类型,Lua是自动内存管理,而C语言则是手动内存管理。为了解决这些问题,Lua的设计者使用了虚拟栈作为二者之间数据交互的介质。在C/C 程序中,如果要获取Lua的值,只需调用Lua的C API函数,Lua就会将指定的值压入栈中。要将一个值传给Lua时,需要先将该值压入栈,然后调用Lua的C API,Lua就会获取该值并将其从栈中弹出。为了可以将不同类型的值压入栈,以及从栈中取出不同类型的值,Lua为每种类型均设定了一个特定函数。

常用API

压入元素

代码语言:javascript复制
    Lua针对每种C类型,都有一个C API函数与之对应,如:
    void lua_pushnil(lua_State* L);  --nil值
    void lua_pushboolean(lua_State* L, int b); --布尔值
    void lua_pushnumber(lua_State* L, lua_Number n); --浮点数
    void lua_pushinteger(lua_State* L, lua_Integer n);  --整型
    void lua_pushlstring(lua_State* L, const char* s, size_t len); --指定长度的内存数据
    void lua_pushstring(lua_State* L, const char* s);  --以零结尾的字符串,其长度可由strlen得出。

   //对于字符串数据,Lua不会持有他们的指针,而是调用在API时生成一个内部副本,因此,即使在这些函
   //数返回后立刻释放或修改这些字符串指针,也不会有任何问题。
   //在向栈中压入数据时,可以通过调用下面的函数判断是否有足够的栈空间可用,一般而言,Lua会预留20
   //个槽位,对于普通应用来说已经足够了,除非是遇到有很多参数的函数。
   //int lua_checkstack(lua_State* L, int extra) --期望得到extra数量的空闲槽位,如果不
   //能扩展并获得,返回false。

类型相关

API使用“索引”来引用栈中的元素,第一个压入栈的为1,第二个为2,依此类推。我们也可以使用为索引值,其中-1表示为栈顶元素,-2为栈顶下面的元素,同样依此类推。

代码语言:javascript复制
    Lua提供了一组特定的函数用于检查返回元素的类型,如: 
    int lua_isboolean (lua_State *L, int index);
    int lua_iscfunction (lua_State *L, int index);
    int lua_isfunction (lua_State *L, int index);
    int lua_isnil (lua_State *L, int index);
    int lua_islightuserdata (lua_State *L, int index);
    int lua_isnumber (lua_State *L, int index);
    int lua_isstring (lua_State *L, int index);
    int lua_istable (lua_State *L, int index);
    int lua_isuserdata (lua_State *L, int index);

以上函数,成功返回1,否则返回0。需要特别指出的是,对于lua_isnumber而言,不会检查值是否为数字类型,而是检查值是否能转换为数字类型。 Lua还提供了一个函数lua_type,用于获取元素的类型,函数原型如下:

代码语言:javascript复制
int lua_type (lua_State *L, int index);

除了上述函数之外,Lua还提供了一组转换函数,如:

代码语言:javascript复制
    int lua_toboolean (lua_State *L, int index);
    lua_CFunction lua_tocfunction (lua_State *L, int index);
    lua_Integer lua_tointeger (lua_State *L, int index);    
    const char *lua_tolstring (lua_State *L, int index, size_t *len);
    lua_Number lua_tonumber (lua_State *L, int index);
    const void *lua_topointer (lua_State *L, int index);
    const char *lua_tostring (lua_State *L, int index);
    void *lua_touserdata (lua_State *L, int index);

对于上述函数,如果调用失败,lua_toboolean、lua_tonumber、lua_tointeger和lua_objlen均返回0,错误的通过判断返回值是否为NULL即可。 对于lua_tolstring函数返回的指向内部字符串的指针,在该索引指向的元素被弹出之后,将无法保证仍然有效。该函数返回的字符串末尾均会有一个尾部0。

查询相关

代码语言:javascript复制
//除了上面给出的数据交换函数之外,Lua的C API还提供了一组用于操作虚拟栈的普通函数,如:
    int lua_gettop(lua_State* L); //返回栈中元素的个数。
    void lua_settop(lua_State* L, int index); //将栈顶设置为指定的索引值。
    void lua_pushvalue(lua_State* L, int index); //将指定索引的元素副本压入栈。
    void lua_remove(lua_State* L, int index); //删除指定索引上的元素,其上面的元素自动下移。
    void lua_insert(lua_State* L, int index); //将栈顶元素插入到该索引值指向的位置。
    void lua_replace(lua_State* L, int index); //弹出栈顶元素,并将该值设置到指定索引上。
    //Lua还提供了一个宏用于弹出指定数量的元素:#define lua_pop(L,n)  lua_settop(L, -(n) - 1) 

执行lua代码

代码语言:javascript复制
void lua_call (lua_State *L, int nargs, int nresults);
int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);

如上,lua_call 中指定了函数的传入参数个数 nargs 和函数返回结果个数 nresults

代码语言:javascript复制
lua_getfield(L, LUA_GLOBALSINDEX, "f"); /* function to be called */
lua_pushstring(L, "how");                        /* 1st argument */
lua_getfield(L, LUA_GLOBALSINDEX, "t");   /* table to be indexed */
lua_getfield(L, -1, "x");        /* push result of t.x (2nd arg) */
lua_remove(L, -2);                  /* remove 't' from the stack */
lua_pushinteger(L, 14);                          /* 3rd argument */
lua_call(L, 3, 1);     /* call 'f' with 3 arguments and 1 result */
lua_setfield(L, LUA_GLOBALSINDEX, "a");        /* set global 'a' */

如果 Lua 代码执行过程中没有任何错误,lua_pcall 的行为与 lua_call 是相同的。如果在执行的过程中有错误发生,lua_pcall 会捕捉该错误,并将错误信息推送到 Lua 栈上,并返回一个错误码。

lua_pcall 最后一个参数 errfunc,指定错误处理函数在 Lua 栈中的位置

一般系统嵌入 Lua 代码,都是使用 lua_pcall,调用方法一般都是:

代码语言:javascript复制
lua_pcall (l, 0, 0, 0)

获取 Lua 代码执行结果

使用 lua_call 或 lua_pcall 执行完一个函数后,会将执行结果放到栈顶,如果有两个返回值,栈索引 -1 和 -2 就是返回值,如果有三个值,栈索引 -1,-2,-3 就是返回值,以此类推

其他常用API

代码语言:javascript复制
lua_State* L=luaL_newstate(); //luaL_newstate()函数返回一个指向堆栈的指针
lua_createtable(L,0,0);	//新建并压入一张表
lua_pushstring(L,0,0);	//压入一个字符串
lua_pushnumber(L,0,0);	//压入一个数字
lua_tostring(L,1);	//取出一个字符串
lua_tointeger(L,1);	//取出数字
double b=lua_tonumber();	//取出一个double类型的数字
lua_load();	//当这个函数返回0时表示加载luaL_loadfile(filename) 
//这个函数也是只允许加载lua程序文件,不执行lua文件。它是在内部去用lua_load()去加载指定名为filename的lua程序文件。当返回0表示没有错误。
luaL_dofile(); //这个函数不仅仅加载了lua程序文件,还执行lua文件。
//返回0表示没有错误。
lua_close(L);	//释放lua资源
lua_getglobal(L, "val");	//获取全局变量的val的值,并将其放入栈顶

案例亲测

1、创建一个文件夹,里面放上 lua.h 文件。文件在哪里自行检索,find 命令不是很好用,可以检索lualib.h。

2、放上一个 test.lua

代码语言:javascript复制
mystr = "I'm lua"

myTable = {name = "xiaomign", id = 123456}

function print_hello()
	print("hello lua")
end

function _add(a, b)
	return a   b
end

3、创建 main.cpp

代码语言:javascript复制
#include <iostream>
#include <string>

#include "lua.hpp"

using namespace std;

int main(int argc, char *argv[])
{
	///< 创建lua句柄并初始化
	lua_State *pState = luaL_newstate();

	if (nullptr == pState)
	{
		cout << "Lua 初始化失败" << endl;
		return -1;
	}

	///< 加载相关库文件
	luaL_openlibs(pState);

	///< 加载lua文件
	if (luaL_loadfile(pState, "./test.lua"))
	{
		cout << "Lua 文件加载失败" << endl;
	}
	else
	{
		///< 执行lua文件
		if (lua_pcall(pState, 0, 0, 0))
		{
			cerr << lua_tostring(pState, -1) << endl;
		}
		else
		{
			///< 获取值
			lua_getglobal(pState, "mystr");

			string str = lua_tostring(pState, -1);
			cout << str << endl;

			///< 获取表中数据
			lua_getglobal(pState, "myTable");
			lua_getfield(pState, -1, "name");
			cout << lua_tostring(pState, -1) << endl;

			lua_getglobal(pState, "myTable");
			lua_getfield(pState, -1, "id");
			cout << lua_tonumber(pState, -1) << endl;

			///< 调用函数
			lua_getglobal(pState, "print_hello");
			lua_pcall(pState, 0, 0, 0);

			///< 调用计算函数
			lua_getglobal(pState, "_add");
			lua_pushnumber(pState, 10);
			lua_pushnumber(pState, 20);

			if (lua_pcall(pState, 2, 1, 0))
			{
				cout << lua_tostring(pState, -1) << endl;
				lua_close(pState);
			}
			else
			{
				if (lua_isnumber(pState, -1))
				{
					cout << lua_tonumber(pState, -1) << endl;
				}
			}
		}
	}

	lua_close(pState);

	return 0;
}

4、编译

代码语言:javascript复制
g   *.cpp -o cpplua -llua -lm -ldl

0 人点赞