Lua C API 研究 —— 基础篇
来源:互联网 发布:努比亚照相机软件下载 编辑:程序博客网 时间:2024/05/21 06:36
Lua C API 研究 —— 基础篇
Lua 提供了和 C 交互的 API,可以在 C 中执行 Lua 代码,也可以在 Lua 中执行 C 代码。两者都通过 Lua C API 实现。本文基于 Lua 5.1,参考 http://www.lua.org/pil/24.html
头文件
在 C/C++ 中使用 Lua C API,需要引入 Lua 的头文件:
lua.h
lua.h 包含了 Lua 的基础函数,这些函数以 lua_ 开头
lauxlib.h
lauxlib.h 包含了 Lua 的辅助函数(称为 auxiliary library 或 auxlib),这些函数以 luaL_ 开头
lualib.h
lualib.h 包含了 Lua 打开内置库的函数,如
- luaopen_base(L); /* opens the basic library */
- luaopen_table(L); /* opens the table library */
- luaopen_io(L); /* opens the I/O library */
- luaopen_string(L); /* opens the string lib. */
- luaopen_math(L); /* opens the math lib. */
在 Lua 5.1 中,可以直接使用 luaL_openlibs 来打开所有库函数,也可以根据需要,只打开需要使用的库
在 C++ 中使用 Lua C API,引用 Lua 头文件时,需要使用 extern “C”:
extern "C" {#include <lua.h>}
Lua 栈
Lua 与 C 通过 Lua 栈(lua_State *L)来进行参数传递,Lua 与 C 的互调,就是通过 Lua C API 对 Lua 栈进行操作
在 Lua 代码中,严格遵守 LIFO 的原则,只能操作 Lua 栈的栈顶。在 C 代码中,则可以操作栈中任意元素,甚至可以在栈的任意位置删除和插入元素
在 Lua 栈中可以存放各种类型的变量,如 number,string,可以存放函数,线程等
如果栈中有 4 个元素,如果以正数来表示,栈顶索引为 4,栈底索引为 1;如果以负数来表示,栈顶索引为 -1,栈底索引为 -4。索引 0 为保留槽,不要去对其进行操作
Lua 栈操作很多,后面会单开一篇进行探讨,这里不再进行详述
C 调用 Lua
应用场景
在 C 中调用 Lua,可以实现在用户的 C 程序中集成一个 Lua 解释器,用于执行 Lua 脚本。这样可以实现系统和业务分离,系统层提供底层能力的支持,业务层使用 Lua 进行编写,可以大大提高业务层的开发效率。另外 C 调用 Lua 时提供了保护执行机制,即使 Lua 代码写得有问题,只会影响当前正在执行的 Lua 实例,不会导致整个系统崩溃
目前比较流行的 Redis 和 Nginx Openresty 中都使用了类似的技术:
- 如 Redis 中加载 Lua 脚本,通过 EVAL 命令可以在 Redis 服务端执行一段 Lua 脚本,以实现类似于存储过程的功能。
- 在 Nginx Openresty 中,可以将 Lua 脚本加载于 Nginx 的配置中,当外部访问指定 URI 时,Nginx 可以执行相应的 Lua 脚本,实现一些复杂的逻辑,如数据库操作,Redis 操作,甚至向外发送 HTTP 请求等
基本流程
C 调用 Lua 基本流程为:
- 引入 Lua 头文件
- 创建 Lua 栈
- 打开需要使用的 Lua 库
- 加载 Lua 代码
- 执行 Lua 代码
- 获取 Lua 代码执行结果
- 关闭 Lua 栈
引入 Lua 头文件
Lua 的头文件为 lua.h,lauxlib.h 和 lualib.h。每个文件包含的内容前面已经介绍过,这里就不再赘述
创建 Lua 栈
lua_open 用于创建一个 Lua 栈,lua_open 实际是一个宏,它调用了 luaL_newstate
#define lua_open() luaL_newstate()lua_State *luaL_newstate(void);
lua_State 即为一个 lua 栈
打开需要使用的 Lua 库
打开 Lua 库对应的函数在 lualib.h 中,前面已经说过,这里不再赘述
加载 Lua 代码
Lua 代码可以通过内存的方式进行加载,也可以通过文件的方式进行加载
int luaL_loadbuffer (lua_State *L, const char *buff, size_t sz, const char *name);int luaL_loadfile (lua_State *L, const char *filename);int luaL_loadstring (lua_State *L, const char *s);
这三个函数底层都调用了 lua_load,该函数会对传入的 Lua 代码进行一次预编译,检查代码中的语法错误。如果没有错误,返回 0,并将编译好的代码块作为函数放到 Lua 栈顶。如果错误,则向栈顶推入一条错误信息,错误信息可以通过 lua_tostring(L, -1) 直接获取
注意,这里的函数这个概念很重要,即使是加载一个 Lua 脚本文件(文件并不是以函数方式进行定义的),在 Lua 中都会把整个文件当成一个函数进行处理,即整个文件就是一个 Lua 函数,并且这个函数放到 Lua 栈的栈顶
执行 Lua 代码
上一步中将 Lua 代码作为函数放到了 Lua 栈顶,就可以通过调用 lua_call 或 lua_pcall 来执行 Lua 代码了
void lua_call (lua_State *L, int nargs, int nresults);int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);
lua_call
如上,lua_call 中指定了函数的传入参数个数 nargs 和函数返回结果个数 nresults,以下是一个官方文档中,lua 函数调用与 lua_call 操作对应关系。里面涉及一些复杂的 Lua 栈操作,后面会单开一张进行探讨。
Lua 代码
a = f("how", t.x, 14)
Lua 代码在 C 中的执行代码
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_call 中指定了几个参数,就需要向 Lua 栈中推入几个参数
lua_pcall
如果 Lua 代码执行过程中没有任何错误,lua_pcall 的行为与 lua_call 是相同的。如果在执行的过程中有错误发生,lua_pcall 会捕捉该错误,并将错误信息推送到 Lua 栈上,并返回一个错误码。
lua_pcall 最后一个参数 errfunc,指定错误处理函数在 Lua 栈中的位置
一般系统嵌入 Lua 代码,都是使用 lua_pcall,调用方法一般都是:
lua_pcall (l, 0, 0, 0)
获取 Lua 代码执行结果
使用 lua_call 或 lua_pcall 执行完一个函数后,会将执行结果放到栈顶,如果有两个返回值,栈索引 -1 和 -2 就是返回值,如果有三个值,栈索引 -1,-2,-3 就是返回值,以此类推
获取这些返回值可以通过栈的操作来实现
关闭 Lua 栈
Lua 栈使用 lua_close 进行关闭
void lua_close (lua_State *L);
示例代码
C 代码:
#include <lua.h>#include <lauxlib.h>#include <lualib.h>#include <stdio.h>void load (char *filename) { lua_State *L = lua_open(); luaopen_base(L); luaopen_io(L); luaopen_string(L); luaopen_math(L); if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0)) if (luaL_loadfile(L, filename)) error(L, "cannot run configuration file: %s", lua_tostring(L, -1)); printf("end lua_call\n"); lua_close(L);}int main(int argc, char** argv) { if (argc != 2) { printf("Usage: %s luafile\n", argv[0]); return -1; } load(argv[1]); return 0;}
lua 代码:
print("Hello World")
编译(我这里装的 luajit,luajit 的链接库放在 /usr/local/lib 下):
$ gcc -g -o aa main.c -I/usr/local/include/luajit-2.1/ -lluajit-5.1
执行:
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib$ ./aa test.lua Hello Worldend lua_call
再来看一下前面提到过的 Lua 的保护机制,我们修改 Lua 代码为:
print("Hello World")a = 10/0print(a)
执行:
$ ./aa test.lua Hello Worldinfend lua_call
Lua 代码除 0 异常退出了,但是,整个 C 代码并没有因此崩溃,而是继续执行,并打印了 end lua_call。这里就点到为止,不再展开了。
Lua 调用 C
Lua 调用 C 的函数,有两种方式,一种是通过 Lua C API,另一种方式是通过 Luajit ffi(个人更喜欢这种方式 ^_^),另外我也单独开过一篇 Luajit ffi 的使用文章 luajit ffi 小结,本文就不再探讨,本文主要探讨的是 Lua C API。
C 函数原型
首先,并不是所有 C 函数都可以使用 Lua C API 进行调用的,能够调用的 C 函数必须遵从 Lua C API 定义的函数原型
typedef int (*lua_CFunction) (lua_State *L);
Lua 每次调用一个 C 函数时,每个 C 函数中传入的 L 都是一个本地栈,这样避免了栈之间的互相干扰。第一个参数在栈中的索引为 1,第二个参数索引为 2,依次类推。C 函数的返回值即为函数返回参数个数。如下面的 Lua 代码
a, b = add(10, 20)
add 对应的 C 函数,函数 Lua 栈索引为 1 的元素为 10,索引为 2 的元素为 20,函数返回值为 2,也就是在函数处理结束前,需要向栈中推入两个元素
C 函数注册
前面一节解决了 Lua 调用 C 函数的方法,但是 Lua 怎么找到 C 函数,这就需要将 C 函数注册到 Lua 的运行栈中,并给它一个 Lua 能够识别的名字
我们使用 lua_pushcfunction 注册函数
void lua_pushcfunction (lua_State *L, lua_CFunction f);
使用 lua_setglobal 指定函数名,该函数实际是弹出栈顶第一个元素,并把该元素设置在全局空间,并给其全局空间的名字
void lua_setglobal (lua_State *L, const char *name);
C 函数注册示例
C 代码:
#include <lua.h>#include <lauxlib.h>#include <lualib.h>#include <stdio.h>static int l_add(lua_State *L) { double a = lua_tonumber(L, 1); double b = lua_tonumber(L, 2); lua_pushnumber(L, a+b); return 1;}void load (char *filename) { lua_State *L = lua_open(); luaopen_base(L); luaopen_io(L); luaopen_string(L); luaopen_math(L); lua_pushcfunction(L, l_add); lua_setglobal(L, "add"); if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0)) if (luaL_loadfile(L, filename)) error(L, "cannot run configuration file: %s", lua_tostring(L, -1)); printf("end lua_call\n"); lua_close(L);}int main(int argc, char** argv) { if (argc != 2) { printf("Usage: %s luafile\n", argv[0]); return -1; } load(argv[1]); return 0;}
Lua 代码:
print("Hello World")a = add(10.1, 20)print(a)
执行:
$ ./aa test.lua Hello World30.1end lua_call
C 链接库
在实际应用中,我们新增加一个函数,并不会像上面那样,把函数和 Lua 解释器放在一起,特别是常用的解释器可能直接是 lua 或 luajit,也不可能直接去修改它们的代码。最常用的方式是将自己要使用的函数封装成 C 链接库,然后通过加载 C 链接库的方式,调用链接库中的 C 函数。
在 C 链接库中怎么注册要使用的函数?如同上面所说,我们不可能去修改 Lua 解释器的代码去注册用户 C 链接库中的函数。实际上 Lua 提供了一个入口函数用于注册 C 链接库中的函数,这个函数的命名方式为 luaopen_*,其执行原理为:当用户在 Lua 中执行 require(“mylib”)时,Lua 解释器就会去寻找 mylib.so,并执行 luaopen_mylib。
luaopen_* 与 Lua C 函数的原型相同,为:
typedef int (*lua_CFunction) (lua_State *L);
C 链接库示例
C 链接库代码:
#include <lua.h>static int l_add(lua_State *L) { double a = lua_tonumber(L, 1); double b = lua_tonumber(L, 2); lua_pushnumber(L, a+b); return 1;}int luaopen_libtest(lua_State *L) { lua_pushcfunction(L, l_add); lua_setglobal(L, "add"); return 1;}
Lua 代码:
require("libtest")print("Hello World")a = add(10.1, 20)print(a)
C 链接库编译:
gcc -g -o libtest.so -shared -fPIC test.c -I/usr/local/include/luajit-2.1/ -lluajit-5.1
执行 Lua 代码:
$ lua test.lua Hello World30.1
这里需要注意,Lua 代码中 require 的库名和 C 链接库中的 luaopen_* 以及最后编译生成的库名必须保持一致
更高级的函数注册
前面使用的 lua_pushcfunction + lua_setglobal 进行函数注册的方法,对于只有一两个函数时问题不大,如果函数比较多,使用起来就比较繁琐,有没有更好的方法来注册函数?答案是有,这种方法就是 luaL_Reg + luaL_register
typedef struct luaL_Reg { const char *name; lua_CFunction func;} luaL_Reg;void luaL_register(lua_State *L, const char *libname, const luaL_Reg *l);
更高级的函数注册示例
我们在上面链接库代码的基础上进行修改,修改 luaopen_libtest 函数实现为
int luaopen_libtest(lua_State *L) { luaL_Reg lua_reg[] = { {"add", l_add}, {NULL, NULL} }; luaL_register(L, "aa", lua_reg); return 1;}
这里使用了 luaL_* 的库,需要增加
#include <lauxlib.h>
luaL_register 第二个参数会创建一个 table,而对注册的函数,都需要使用该 table 进行引用,因此 Lua 代码修改为:
require("libtest")print("Hello World")a = aa.add(10.1, 20)print(a)
执行 Lua 代码:
$ lua test.lua Hello World30.1
Lua 和 C 互调模型小结
C 调用 Lua 函数
- C 调用 Lua,实际上是先向栈顶压入 Lua 函数
- 再向栈顶依次压入参数
- 调用 lua_call 或 lua_pcall 执行 lua 函数(这里决定了函数在栈中的位置)
- lua 函数执行完成,向栈顶压入返回值
- 取出返回值,得到函数执行结果
我们以一个带 2 个参数,返回一个值的函数为例,函数调用方式为:
add(20.1, 10)
前面说过,自己写 Lua 解释器,在载入 Lua 文件时,实际是将整个文件作为一个函数压入到 Lua 栈顶,因为一般情况下没有传入参数,也没有返回参数,所以调用函数的参数个数和返回值个数都为 0
Lua 加载 C 链接库
- require 找到对应的 C 链接库(lua 库搜索路径为 package.path,C 库搜索路径为 package.cpath)
执行 C 链接库中的 luaopen_*,这个 * 与 require 的传入参数相同,如果不同会报以下错误
./libtest.so: undefined symbol: luaopen_libtest
Lua 执行 C 函数
- 通过名字找到 C 函数的函数指针
- 创建一个 local Lua 栈
- 将传入参数压入 Lua 栈中
- 执行 C 函数
- C 函数将返回值压入 Lua 栈中
- C 函数返回返回值的个数
我们以一个带 2 个参数,返回一个值的函数为例,该函数注册名为 add,注册函数指针为 l_add,函数调用方式为:
add(20.1, 10)
- Lua C API 研究 —— 基础篇
- lua学习总结——C API
- Lua与C通信——Lua API(一)
- lua学习笔记(3)——C API基础和栈
- Lua和C交互——C API
- lua c api
- LUA C API
- lua C Api 简介
- lua-C API
- 【Lua】C-API
- lua C API
- LUA C API接口
- FreeSwitch LUA API ——API Events
- FreeSwitch LUA API ——API Sessions
- Lua学习笔记——基础篇
- C++ – Lua C API
- lua的c api 总结
- lua的c api 总结
- 从相机(相册)获取图片并剪裁的最佳实践
- 归并排序
- 浏览器检测
- Android事件处理
- rpm/yum命令记录
- Lua C API 研究 —— 基础篇
- linux unlink函数
- Poj 2750 Potted Flower
- STUN/TURN/ICE协议在P2P SIP中的应用(二)
- Java中守护线程的总结
- UI设计师未来的4个发展方向
- 安卓学习_基于TCP协议的网络通信
- mysql-5.6.24-winx64 在win10安装
- Rust 中项目构建管理工具 Cargo简介