欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C语言开发lua模块入门 --- 虚拟栈和基本代码结构

程序员文章站 2022-12-10 15:29:23
lua是一门小巧高效的脚本语言,核心代码不到500kb,由于要保持小巧,所以lua的核心库功能不可能很复杂,只实现一些基本功能,甚至没有操作目录的API,但由于lua良好的设计,并...

lua是一门小巧高效的脚本语言,核心代码不到500kb,由于要保持小巧,所以lua的核心库功能不可能很复杂,只实现一些基本功能,甚至没有操作目录的API,但由于lua良好的设计,并且是用标准C开发,所以很容易用C语言来扩展lua的功能。

用C编写lua模块需要遵循一定的规则,由于C和lua中的数据结构是完全不一样的,并且lua函数可以返回多个结果,所以要有一种在lua脚本和C函数之间交换数据的机制,这个机制就是虚拟栈,这个栈可以储存任何类型的数据,供lua调用的C函数从这个栈中获取调用参数,然后将结果再压入栈中返回到lua,这个虚拟栈用索引来操作,栈底(第一个入栈)的元素索引为1, 第二个入栈的索引为2,依次类推...,这个虚拟栈也支持逆向索引,即栈顶(最后一个入栈)的元素索引为-1,倒数第二个入栈的为-2,依次类推,这两种索引方式依据实际情况都很常用。

lua的C API提供了很多的从栈中读取数据以及向栈中添加数据的基本方法,下面列出一些常用方法:

 

void lua_pushboolean (lua_State *L, int b); //向栈中压入一个bool值
void lua_pushinteger (lua_State *L, lua_Integer n); //压入一个整数
void lua_pushnil (lua_State *L);  //压入一个nil
void lua_pushnumber (lua_State *L, lua_Number n); //压入一个双精度数
void lua_pushstring (lua_State *L, const char *s);  //压入一个字符串

int lua_toboolean (lua_State *L, int index);  //从栈中读取一个bool值,index为读取的数据在栈中的索引
lua_Integer lua_tointeger (lua_State *L, int index);  //读取一个整数
lua_Number lua_tonumber (lua_State *L, int index);  //读取一个浮点数
const char *lua_tostring (lua_State *L, int index); //读取一个字符串

以上方法第一个参数都是 lua_State *L, 我们可以认为该参数代表了虚拟栈, int index是需要读取的元素在栈中的索引。

 

 

由于为lua编写的C函数从虚拟栈中读取参数,然后再把返回结果添加到栈中,所以为lua编写的C函数都只需要一个参数,就是这个虚拟栈 lua_State *L, 下面是C函数的函数原型:

 

int funcname(lua_State *L);

 

int类型的返回值表示该函数返回值的个数,下面是一个求两个数和的C函数示例:

 

int sum(lua_State *L)
{
	int a = lua_tointeger(L, 1); //第一个加数,函数的第一个参数总是索引1
	int b = lua_tointeger(L, 2);  //第二个加数
	lua_pushinteger(L, a + b);  //压入结果
	return 1;					//返回1表示该方法只有一个返回值
}

函数写好了,那lua怎么才能调用该函数呢,第一种方法是使用lua_pushcfunction方法在lua的源码中直接注册这个函数,然后重新编译lua就可以使用这个方法了,第二种方法把这个函数编译进一个lua C module,然后放入lua加载C模块的路径中,代码中require这个模块即可。由于第一种方法需要修改源码,并且每次修改C函数都需要重新编译lua,显然很麻烦,所以这里主要介绍第二种方法。

 

编写C模块有固定的代码结构:

 

//加载lua头文件
#include 

//编写C函数 static使外部无法直接访问这个函数
static int sum(lua_State *L)
{
	int a = lua_tointeger(L, 1); //第一个加数,函数的第一个参数总是索引1
	int b = lua_tointeger(L, 2);  //第二个加数
	lua_pushinteger(L, a + b);  //压入结果
	return 1;					//返回1表示该方法只有一个返回值
}

//声明一个luaL_Reg结构体数组
static const struct luaL_Reg funcs[] = {
	{"sum", sum},  //该结构体第一个成员是一个字符串,表示C模块中该函数的名称,第二个成员是指向该函数的指针
	{NULL, NULL}	// 该数组最后一个元素始终是 {NULL, NULL}
};

//最后注册这个数组,并给这个C模块定义一个名字,注意,这个函数名不是固定的,如果你的模块名叫xxx,那么这个函数名就应该是 luaopen_xxx
int luaopen_clib(lua_State *L)
{
	luaL_register(L, "clib", funcs);	//第二个参数要和你的模块名一致
	return 1;
}

把这个C文件编译成动态链接库,动态链接库的文件名命名规则为 xxx.so, 其中xxx就是你在C文件中定义的模块名

 

 

gcc -fPIC -c clib.c -I/usr/include     # -I指定lua头文件的位置
gcc -shared clib.o /usr/lib64/liblua-5.1.so -o clib.so  # /usr/lib64/liblua-5.1.so 为你的lua库路径,根据你的实际路径修改

把编译好的clib.so放到你的lua C模块路径下,那怎么知道自己lua加载C模块的路径呢,在lua脚本中打印一下 package.cpath 就可以获取到这个路径

 

 

[root@localhost lua-c-module]# lua
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> print(package.cpath)
./?.so;/usr/lib64/lua/5.1/?.so;/usr/lib64/lua/5.1/loadall.so
> 
可以看到 我这里的cpath路径有当前目录 ./ 和 /usr/lib64/lua/5.1/ , 所以 我把 clib.so 移动到/usr/lib64/lua/5.1/ 目录下

编写lua测试脚本

 

local clib = require "clib"

local s = clib.sum(3, 99)

print(s)

执行结果:

 

 

[root@localhost lua]# lua test.lua 
102

这样,一个简单的clib模块就开发好了,是不是也很简单呢,想要更深入的了解lua C模块的编写技术,欢迎关注后续文章:)。