Lua本身的设计时就有为了可以方便的嵌入到别的语言中使用的功能,主要来说就是可以方便的嵌入到C语言中,并和C语言进行交互。这篇文章将对相关内容进行介绍。
交互可以有多方面意思,可以直接与现成的Lua解释器交互,可以将Lua编译成DLL交互,也可以将直接Lua嵌入代码中在进行函数相互调用等。这篇文章将以后者进行展开,下面就是个最基本的演示过程。
首先从官网下载Lua源码:https://www.lua.org/
解压得到源码,将其中的除 lua.c
和 luac.c
(这两个一个是解释器、一个是编译器)外的代码导入到C语言项目中,然后使用下面代码就能编译测试:
#include
#include
#include "lua-5.4.6/src/lua.h" // Lua数据类型与函数接口
#include "lua-5.4.6/src/lauxlib.h" // Lua与C交互辅助函数接口
#include "lua-5.4.6/src/lualib.h" // Lua标准库打开接口
int main()
{
lua_State* L = luaL_newstate(); // 创建Lua线程
luaL_openlibs(L); // 打开标准库
luaL_dostring(L, "print('Naisu, Lua!')"); // 解析并执行Lua脚本字符串
lua_close(L); // 关闭Lua线程
return 0;
}
Lua与C交互所需的东西都在 lua.h
中有声明,相关内容可以参考官方文档的《The Application Program Interface》章节
另外为了简化交互操作,还提供了 lauxlib.h
,这个中的很多操作是对前者进一步封装,相关内容可以参考《The Auxiliary Library》章节。
Lua和C语言的数据类型是不同的,所以没法直接交互,需要通过Lua的虚拟的栈结构进行交互。栈中的每个元素代表一个Lua的值。
Lua提供了非常多的函数用于C语言操作栈,比如使用 void lua_pushXXXX (lua_State *L, XX)
可以向栈压入数据,使用 XX lua_isXXXX (lua_State *L, int index)
可以判断栈中某个索引的值是否为某个类型,使用 XX lua_toXXXX (lua_State *L, int index)
获取栈中某个索引的值为特定类型。
Lua的栈可以正索引也可以负索引。默认最小大小由 lua.h
中 #define LUA_MINSTACK 20
定义。
下面代码可以简单进行Lua栈的测试:
lua_State* L = luaL_newstate();
luaL_openlibs(L);
printf("Top index: %d\n", lua_gettop(L)); // 返回当前栈顶索引(等于栈中元素个数)
lua_pushnumber(L, 233); // 压数据入栈
lua_pushstring (L, "Naisu"); // 压数据入栈
printf("Top index: %d\n", lua_gettop(L));
printf("Index 2: %s\n", lua_tostring(L, 2)); // 将栈中数据转换成C语言数据
printf("Index 1: %d\n", lua_tointeger(L, 1)); // 将栈中数据转换成C语言数据
// 这类转换如果失败则给出默认值0或NULL
printf("Top index: %d\n", lua_gettop(L));
lua_pop(L, 1); // 从栈中弹出一个值
printf("Top index: %d\n", lua_gettop(L));
lua_settop(L, 0); // 清空栈
printf("Top index: %d\n", lua_gettop(L));
lua_close(L);
lua.h
和 lauxlib.h
中有非常多的函数可以用来操作Lua栈,这里不具体进行介绍,有兴趣的可以查看官方文档。
C语言函数只有符合下面格式,并且注册到Lua中才能通过Lua脚本调用:
typedef int (*lua_CFunction) (lua_State *L)
下面是个最基础的演示:
static int sayhello (lua_State *L) {
printf("Hello Naisu\n");
return ;
}
int main() {
lua_State* L = luaL_newstate(); // 创建Lua线程
luaL_openlibs(L);
lua_register(L, "cfn_sayhello", sayhello); // 将C语言的函数sayhello以名称cfn_sayhello注册为Lua的全局函数
luaL_dostring(L, "cfn_sayhello()"); // 解析并执行Lua脚本字符串
lua_close(L); // 关闭Lua线程
return 0;
}
注册C函数到Lua中有很多的方式,比如也可以学 luaL_openlibs(L)
操作内部注册标准库的方式
static int sayhello (lua_State *L) {
printf("Hello Naisu\n");
return 0;
}
static luaL_Reg Functions[] =
{
{"sayhello", sayhello}, // 函数名和函数指针
// 可以一次添加多个函数
{NULL, NULL} // 数组末尾必需有这个
};
LUAMOD_API int luaopen_hello (lua_State *L) {
luaL_newlib(L, Functions); // 将Functions注册为一个库
return 1;
}
int main() {
lua_State* L = luaL_newstate(); // 创建Lua线程
luaL_openlibs(L); // 打开标准库
luaL_requiref(L,"hello", luaopen_hello, 0); // 通过luaopen_hello将Functions注册为hello库
// 当最后的参数为1时,注册完成时会将库直接导入到Lua全局变量中
lua_pop(L, 1); // 清除堆栈
luaL_dostring(L, "hello = require(\"hello\")\
hello.sayhello()"); // 解析并执行Lua脚本字符串
// 如果luaL_requiref最后参数为1,则这里的Lua脚本就不需要require
lua_close(L); // 关闭Lua线程
return 0;
}
在Lua中调用的C函数的传入参数和返回参数操作都是通过前面提到的栈来进行的:
#include
#include
#include "lua-5.4.6/src/lua.h"
#include "lua-5.4.6/src/lauxlib.h"
#include "lua-5.4.6/src/lualib.h"
// 下面函数的作用是传入两个整数,如果参数正确则求和并返回结果和nil,错误则返回0和错误信息
static int add (lua_State *L) {
printf("Call add:");
printf("Top index: %d\n", lua_gettop(L)); // 在Lua中调用C函数,每次调用函数都有自己的堆栈
if ((lua_gettop(L)==2)&&lua_isinteger(L, 1)&&lua_isinteger(L, 2)) // 输入两个参数并且都是整数
{
int var1 = lua_tointeger(L, 1); //获取参数1
int var2 = lua_tointeger(L, 2); //获取参数2
int sum = var1 + var2;
lua_settop(L, 0); // 清空栈
lua_pushinteger(L, sum); // 将返回值压入栈
lua_pushnil(L); // 压入栈
}
else
{
lua_settop(L, 0); // 清空栈
lua_pushinteger(L, 0); // 压入栈
lua_pushstring(L, "Wrong arg!"); // 压入栈
}
return 2; // 表示返回两个参数(栈顶的两个)
}
const char *lua_code = "\
local ret, err = add(22, 33)\
print(ret, err)\
ret, err = add(22, 33, 44)\
print(ret, err)\
";
int main() {
lua_State* L = luaL_newstate(); // 创建Lua线程
luaL_openlibs(L);
printf("Top index: %d\n", lua_gettop(L));
lua_register(L, "add", add); // 注册函数
luaL_dostring(L, lua_code); // 调用add函数,传入两个参数
lua_close(L);
return 0;
}
Lua可以调用C函数,C也可以调用Lua函数,数据交互也是通过栈进行:
#include
#include
#include "lua-5.4.6/src/lua.h"
#include "lua-5.4.6/src/lauxlib.h"
#include "lua-5.4.6/src/lualib.h"
// 下面定义了一个lua函数,传入两个参数并打印,返回22,33
const char *lua_code = "\
function lua_func(arg1, arg2)\
print(arg1, arg2)\
return 22, 33\
end\
";
int main() {
lua_State* L = luaL_newstate(); // 创建Lua线程
luaL_openlibs(L);
luaL_dostring(L, lua_code); // 加载自定义的lua函数到全局变量
printf("Top index: %d\n", lua_gettop(L));
lua_getglobal(L, "lua_func"); // 从全局变量中获取自定义lua函数压入栈中
lua_pushinteger(L, 666); // 向栈中压入参数
lua_pushinteger(L, 777); // 向栈中压入参数
printf("Top index: %d\n", lua_gettop(L));
lua_call(L, 2, LUA_MULTRET); // 调用已压入栈中的lua函数,传入2个参数,并将所有返回值压入栈中
// lua_call调用函数本身会清空当前栈,然后再将返回值压入栈
printf("Top index: %d\n", lua_gettop(L));
printf("Index 1: %s\n", lua_tostring(L, 1)); // 打印lua函数返回值
printf("Index 2: %s\n", lua_tostring(L, 2)); // 打印lua函数返回值
lua_close(L);
return 0;
}
Lua和C语言交互本身并不复杂,毕竟Lua也是由C编写的,这里整个源码都一起编译了。
相对内容比较多的就是数据交互了,总体来说都是通过抽象的栈进行的,但本文中只是展示了一些基础的内容,实际上为了应对各种场景,Lua提供了非常多的接口,可以根据需求参考官方文档和源码中的接口等。
另外本文还未涉及错误处理等内容,也可以参考官方文档和源码来处理。