Lua与C交互是一种非常常见的技术,它可以让我们在C语言中调用Lua脚本,也可以让我们在Lua脚本中调用C语言函数。这种交互方式可以使得我们在不同的编程语言之间实现混合编程,从而达到更高的灵活性和效率。
Lua栈是Lua与C交互的核心数据结构,所有的数据交互都是通过Lua栈来完成的。无论是从Lua中调用C函数,还是从C中调用Lua函数,都需要使用Lua栈来传递参数和返回值。
Lua与C的基本交互是通过Lua提供的C API来实现的。C API是一组C语言函数,它们可以让我们在C语言中直接操作Lua对象,包括创建Lua对象、读取和修改Lua对象的值、执行Lua代码等。
lua.h
lua.h
是 Lua 解释器的核心头文件,包含了 Lua 解释器的基本数据类型和函数原型。在使用 Lua 解释器时,必须包含该头文件。
常用的数据类型包括:
lua_State
:Lua 解释器状态,用于保存 Lua 程序的运行状态和数据栈。lua_Number
:Lua 的数字类型,可以是整数或者浮点数。lua_Integer
:Lua 的整数类型,通常用于表示数组下标或者计数器。lua_CFunction
:C 函数指针类型,用于定义 Lua 扩展函数。lua_XXX
:其他常用数据类型,如字符串、表、函数等。常用的函数原型包括:
lua_newstate
:创建一个 Lua 解释器状态。lua_close
:关闭一个 Lua 解释器状态。lua_load
:将 Lua 代码加载到解释器中。lua_pcall
:调用 Lua 函数并处理异常。lua_pushXXX
:将数据压入 Lua 数据栈中。lua_toXXX
:从 Lua 数据栈中取出数据。lualib.h
lualib.h
是 Lua 标准库的头文件,包含了一些常用的函数和库的函数原型。在使用 Lua 标准库时,必须包含该头文件。
常用的库包括:
lua_math
:数学库,提供了常用的数学函数。lua_string
:字符串库,提供了字符串操作函数。lua_table
:表库,提供了表操作函数。lua_io
:I/O 库,提供了文件和网络操作函数。lua_os
:操作系统库,提供了系统调用函数。常用的函数原型包括:
luaL_newlib
:创建一个 Lua 标准库扩展。luaL_loadfile
:加载一个 Lua 脚本文件。luaL_dofile
:加载并执行一个 Lua 脚本文件。luaL_checkXXX
:从 Lua 数据栈中取出数据并检查类型。luaL_optXXX
:从 Lua 数据栈中取出可选参数并设置默认值。luaL_error
:抛出一个 Lua 异常。lauxlib.h
lauxlib.h 是 Lua 辅助库的头文件,包含了一些辅助函数和库的函数原型。在编写 Lua 扩展模块或者使用 Lua 辅助库时,必须包含该头文件。
常用的函数包括:
luaL_checkversion
:检查 Lua 版本是否兼容。luaL_checkstack
:检查 Lua 数据栈是否有足够的空间。luaL_newmetatable
:创建一个新的元表。luaL_setmetatable
:将元表设置到一个 Lua 对象上。luaL_ref
:将 Lua 对象的引用压入数据栈并返回引用索引。luaL_unref
:释放一个 Lua 对象的引用。luaL_loadbuffer
:将 Lua 代码加载到解释器中。luaL_typename
:获取 Lua 对象的类型名称。luaL_argerror
:抛出一个参数错误异常。更多更详细的C API可参考(http://www.lua.org/manual/5.4/)。
简单示例:
makefile
:
这里用的是luajit,如果是lua就等价换为 -llua。
CC=gcc
CFLAGS=-Wall -g
LDFLAGS=-lluajit-5.1 -lm
LDFLAGS += -L/usr/local/lib
.PHONY: all clean
all: main
main: main.c
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
clean:
rm -f *.o main
main.c
:
#include
#include
#include
#include
#include // luajit才需要
void dump(lua_State *L) {
int n = lua_gettop(L);
for (int i = 1; i <= n; i++) {
int type = lua_type(L, i);
const char* str = lua_tostring(L, i);
printf("%d: %s(%s)\n", i, str, lua_typename(L, type));
}
}
int main() {
lua_State *L = luaL_newstate();// 创建Lua状态机
luaL_openlibs(L);// 打开Lua标准库
if (luaL_loadfile(L, "test.lua") || lua_pcall(L, 0, 0, 0)) {// 加载并执行test.lua文件
printf("Error: %s\n", lua_tostring(L, -1));// 如果出错,打印错误信息
return 1;
}
lua_getglobal(L, "add");// 获取全局变量add
lua_pushnumber(L, 10);// 压入第一个参数
lua_pushnumber(L, 5);// 压入第二个参数
lua_pcall(L, 2, 1, 0);// 调用add函数并传递两个参数,期望返回1个结果
if (lua_isnumber(L, -1)) {// 如果返回值是一个数字
double res = lua_tonumber(L, -1); // 获取返回值
printf("res = %f\n", res); // 打印返回值
}
lua_getglobal(L, "sub");
lua_pushnumber(L, 10);
lua_pushnumber(L, 5);
lua_pcall(L, 2, 1, 0);
if (lua_isnumber(L, -1)) {
double res = lua_tonumber(L, -1);
printf("res = %f\n", res);
}
dump(L);
/* lua_tonumber() 不会弹出元素
1: 15(number)
2: 5(number)
*/
lua_close(L);// 销毁Lua状态机
return 0;
}
test.lua
:
function add(a, b)
return a + b
end
function sub(a, b)
return a - b
end
更复杂的案例:
#include
#include
#include
#include
extern "C" {
#include
#include
#include
}
#include
using namespace std;
using namespace luabridge;
/*
* _G = {
* printMsg = function
* my_pow = function
* }
*/
const char* script1 = R"(
function printMsg()
print("Hello World")
end
printMsg()
function my_pow(x, y) -- 要拿到函数function 好像local my_pow = function 不可:update(2023年5月11日):全局函数!
return x ^ y
end
)";
/*
* _G = {
* "pkg" = {
* {__index = _G}
* printMsg
* my_pow
* }
* }
*/
const char* script2 = R"(
print("script2")
pkg.printMsg()
)";
void call_errno(lua_State *L) {
if (lua_isstring(L, -1)) { // 1 true; 0 false
auto msg = lua_tostring(L, -1);
printf("load script1 failed : %s\n", msg);
lua_pop(L, 1);
}
}
int main() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
// -> lua_load (load chunk to stack && exec chunk) (chunk 的第一个upvalue是_ENV )
auto res = luaL_loadbuffer(L, script1, strlen(script1), "script1");
if (res != LUA_OK) { // load script1 failed -> return msg to the top of stack
call_errno(L);
return -1;
}
// call the chunk
if (lua_pcall(L, 0, 0, 0) != LUA_OK) { // output : Hello World
call_errno(L);
return -1;
}
// call my_pow()
res = lua_getglobal(L, "my_pow");
const char *a = lua_tostring(L, -1);
if (!lua_isfunction(L, -1)) {
call_errno(L);
return -1;
}
// [stack] -> chunk ; my_pow;
lua_pushnumber(L, 2);
lua_pushnumber(L, 3); // [stack] -> chunk; my_pow; 2; 3;
if (lua_pcall(L, 2, 1, 0) != LUA_OK) { // [stack] -> chunk; my_pow; 2^3;
call_errno(L);
return -1;
} // [stack] -> chunk; 8.0;
if (!lua_isnumber(L, -1)) {
call_errno(L);
return -1;
}
res = lua_tonumber(L, -1);
lua_pop(L, 1); // pop 8
printf("call function (my_pow) result : %d\n", res);
// clear stack
lua_settop(L, 0); // num = lua_gettop(L); lua_pop(L, num);
/* -----------------------以上是对加载script1的简单使用-------------------------------------------- */
// local script1 && package
// 以下不做过多的判误操作了,默认写的全对( ^_^ )
luaL_loadbuffer(L, script1, strlen(script1), "script1");
lua_getglobal(L, "_G"); // [stack] -> chunk; _G;
lua_newtable(L); // [stack] -> chunk; _G; newtable;
lua_pushstring(L, "pkg"); // [stack] -> chunk; _G; newtable; "pkg";
lua_pushvalue(L, -2); // [stack] -> chunk; _G; newtable; "pkg"; newtable;
// rawset -> (t[k] = v); -2: key; -1: val; t: second param;
lua_rawset(L, -4); // [stack] -> chunk; _G; newtable;
// setupvalue -> (param: funcindex, n) (index处的函数设置第n个upvalue)
const char * upvalueName = lua_setupvalue(L, -3, 1); // [stack] -> chunk; _G;
assert(strcmp(upvalueName, "_ENV") == 0);
lua_newtable(L); // [stack] -> chunk; _G; metatable;
lua_pushstring(L, "__index"); // [stack] -> chunk; _G; metatable; "__index";
lua_pushvalue(L, -3); // [stack] -> chunk; _G; metatable; "__index"; _G;
lua_rawset(L, -3); // [stack] -> chunk; _G; metatable;
lua_pushstring(L, "pkg"); // [stack] -> chunk; _G; metatable; "pkg";
// rawget -> (t[k]) -1: key; t: second param;
lua_rawget(L, -3); // [stack] -> chunk; _G: metatable; pkg(table);
lua_pushvalue(L, -2); // [stack] -> chunk; _G: metatable; pkg(table); metatable;
lua_setmetatable(L, -2); // [stack] -> chunk; _G: metatable; pkg(table);
lua_pop(L, 3); // [stack] -> chunk;
luaL_loadbuffer(L, script2, strlen(script2), "script2");
lua_pcall(L, 0, 0, 0);
lua_close(L);
return 0;
}
简单示例:
main.c
:
#include
#include
#include
#include
#include
int add(lua_State *L) {// 定义一个C函数
double a = luaL_checknumber(L, 1);// 从Lua栈中获取第一个参数
double b = luaL_checknumber(L, 2);// 从Lua栈中获取第二个参数
double res = a + b;
lua_pushnumber(L, res);// 将结果压入Lua栈中
return 1;// 返回值的数量
}
int main() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
lua_pushcfunction(L, add);// 注册add函数
lua_setglobal(L, "add");// 将add函数设置为全局变量
if (luaL_loadfile(L, "test.lua") || lua_pcall(L, 0, 0, 0)) {// 加载并执行test.lua文件
printf("Error: %s\n", lua_tostring(L, -1));
return 1;
}
lua_close(L);
return 0;
}
test.lua
:
local res = add(10, 5)
print(res)
更复杂的案例:
#include
#include
#include
#include
extern "C" {
#include
#include
#include
}
#include
using namespace std;
using namespace luabridge;
const char *script = R"(
local val = pow_from_c(2, 3);
print(val)
)";
/**
* _G = {
* "pow_from_c" = function
* }
*/
int pow_from_c(lua_State *L) {
int param_cnt = lua_gettop(L);
if (param_cnt != 2) return 0;
if ( lua_isinteger(L, 2) && lua_isinteger(L, 1) ) {
auto x = lua_tonumber(L, 1);
auto y = lua_tonumber(L, 2);
int res = (int)pow(x, y);
lua_pushnumber(L, res);
return 1;
}
return 0;
}
int main() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
lua_getglobal(L, "_G");
lua_pushstring(L, "pow_from_c");
lua_pushcclosure(L, pow_from_c, 0); // [stack] -> _G; "pow_from_c"; closure;
lua_rawset(L, -3);
lua_pop(L, 1);
luaL_loadbuffer(L, script, strlen(script), "script");
lua_pcall(L, 0, 0, 0);
lua_close(L);
return 0;
}
大部分情况下,我们还是用模块的方式来提供一组函数,我们在Lua中可以加载这个模块。
注释中有拓展一些知识点
main.cpp
:
#include
#include
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#include
extern "C" int luaopen_mylib(lua_State *L);
#ifdef __cplusplus
}
#endif
using namespace std;
// a + b
static int l_add(lua_State* L) {
double a = luaL_checknumber(L, 1); // luaL_check*: 检查符合指定类型转换并返回;不是则抛出错误,中断函数执行。
// luaL_to*: 转换失败返回默认值
double b = luaL_checknumber(L, 2);
lua_pushnumber(L, a + b);
return 1;
}
// a / b
static int l_div(lua_State* L) {
double a = luaL_checknumber(L, 1);
double b = luaL_checknumber(L, 2);
if (b == 0) {
luaL_error(L, "division by zero");
}
lua_pushnumber(L, a / b);
return 1;
}
// ab
static int l_concat(lua_State* L) {
const char* a = luaL_checkstring(L, 1);
const char* b = luaL_checkstring(L, 2);
lua_pushstring(L, a);
lua_pushstring(L, b);
lua_concat(L, 2);
return 1;
}
// userdata: 一块内存区域,可以在C代码中分配和管理,然后传递给Lua脚本使用
// lua_touserdata 获取对应指针
// 被gc检测没被引用时,会调用C代码中定义的__gc元方法回收。
typedef struct {
int x, y;
} Point;
static int l_newpoint(lua_State* L) {
Point *p = (Point *)lua_newuserdata(L, sizeof(Point));
p->x = luaL_checknumber(L, 1);
p->y = luaL_checknumber(L, 2);
return 1;
}
static int l_getpoint(lua_State* L) {
Point *p = (Point *)lua_touserdata(L, 1);
cout << "[ x = " << p->x << ", y = " << p->y << " ]" << endl;
return 1;
}
// light userdata: 指针,通常用于将一些C语言中的数据结构传给Lua脚本
// lua_topointer 获取对应指针
// 需要显示释放这块内存,没被引用时不会调用任何元方法。
static int l_newlight(lua_State* L) {
Point* p = (Point *)malloc(sizeof(Point));
p->x = luaL_checknumber(L, 1);
p->y = luaL_checknumber(L, 2);
lua_pushlightuserdata(L, p); // 返回一个指针,由C自己管理
return 1;
}
static int l_getlight(lua_State* L) {
Point *p = (Point *)lua_topointer(L, 1);
cout << "[ x = " << p->x << ", y = " << p->y << " ]" << endl;
return 0;
}
// 注册名为 `mylib` 的Lua模块,最后一项用于数组标记结尾
// luaL_Reg结构体有两个成员:函数名和函数指针
static const struct luaL_Reg mylib[] = {
{"add", l_add},
{"div", l_div},
{"concat", l_concat},
{"newpoint", l_newpoint},
{"getpoint", l_getpoint},
{"newlight", l_newlight},
{"getlight", l_getlight},
{NULL, NULL}
};
// Lua C API 中,所有用于打开和关闭模块的函数都应该以:
// luaopen_ 前缀开头。
int luaopen_mylib(lua_State *L) {
// luaL_newlib(L, mylib); // 适用于创建新的模块表并注册函数
luaL_register(L, "mylib", mylib); // 适用于向已有的表中注册函数 (最新版本的Lua:已弃用)
return 1;
} // 将一组C函数注册到表中,然后将这个表返回给Lua 解释器
test.lua
:
local mylib = require "mylib"
local add = mylib.add
local div = mylib.div
local concat = mylib.concat
local newpoint = mylib.newpoint
local getpoint = mylib.getpoint
local newlight = mylib.newlight
local getlight = mylib.getlight
--[[
-- 在lua中,每个函数调用都会创建一个新的栈帧,栈帧是一个独立的空间,用于存储改函数执行时需要的数据。
--]]
print(add(1, 2))
print(div(1, 2))
local s1 = "Hello"
local s2 = "World"
print(concat(s1, s2))
local x, y = 3, 4
local p = newpoint(x, y)
getpoint(p)
local p1 = newlight(x + 1, y - 1)
getlight(p1)
该段代码执行的makefile
文件:
CC=g++
CFLAGS=-Wall -g
LDFLAGS=-shared -fPIC -lluajit-5.1 -lm
LDFLAGS += -L/usr/local/lib
TARGET=mylib.so
SRC=main.cpp
all: $(TARGET)
$(TARGET): $(SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
clean:
rm -f $(TARGET)
拓README
:(只是用于记录)
[ 编译.o 文件 ]
- g++ *.cpp -c -I../include -L../lib -llua
----
- -llua : [ 我在 ../lib 中用 *.o 生成了一个 lua.a ]
- liblua.a 在lib、include都有,不用自己生成
[ 生成静态库 .a ]
- ar rs *.a *.o
[ 生成动态库 .so ]
- g++ *.cpp -I../include -fPIC -shared -o *.so
数字类型转换:Lua中的数字类型为lua_Number,它可以是double或long long等类型。在C语言中,我们可以使用lua_Number类型来表示Lua中的数字类型。
字符串类型转换:Lua中的字符串类型为const char*,在C语言中也是使用char*类型表示。在Lua中,字符串可以包含任意的二进制数据,因此在C语言中读取Lua字符串时需要进行一些特殊处理。
表类型转换:Lua中的表类型可以使用lua_newtable函数创建,然后使用lua_settable或lua_rawset函数向表中添加元素。在C语言中,我们可以使用lua_gettable或lua_rawget函数获取表中的元素。
函数类型转换:Lua中的函数类型可以通过lua_pushcfunction函数将一个C函数压入Lua栈中。在C语言中,我们可以通过lua_isfunction和lua_tocfunction函数判断和获取Lua栈中的函数。
Lua中的数据类型可以通过lua_State结构体来访问,而C中的数据类型可以通过特定的函数来进行访问。
Lua中的数据类型可以通过压栈和弹栈的方式来进行传递,而C中的数据类型则需要通过参数传递或返回值来进行传递。
常见的数据类型转换方法:
int lua_tointeger(lua_State *L, int index); //将指定位置的栈值转换为整数
long lua_tointegerx(lua_State *L, int index, int *isnum); //将指定位置的栈值转换为长整数
double lua_tonumber(lua_State *L, int index); //将指定位置的栈值转换为浮点数
double lua_tonumberx(lua_State *L, int index, int *isnum); //将指定位置的栈值转换为浮点数
const char *lua_tostring(lua_State *L, int index); //将指定位置的栈值转换为字符串
size_t lua_strlen(lua_State *L, int index); //获取指定位置的栈值的长度
void lua_pushinteger(lua_State *L, lua_Integer n); //将整数压入栈中
void lua_pushnumber(lua_State *L, lua_Number n); //将浮点数压入栈中
void lua_pushstring(lua_State *L, const char *s); //将字符串压入栈中
void lua_pushlightuserdata(lua_State *L, void *p); //将指针压入栈中
int lua_gettable(lua_State *L, int index); //从栈中获取指定位置的table,并将其压入栈中
int lua_getfield(lua_State *L, int index, const char *k); //从栈中获取指定位置的table的指定字段,并将其压入栈中
int lua_rawgeti(lua_State *L, int index, lua_Integer n); //从栈中获取指定位置的table的指定索引,并将其压入栈中
void lua_newtable(lua_State *L); //创建一个新的空table,并将其压入栈中
void lua_settable(lua_State *L, int index); //从栈中获取指定位置的table和key-value对,并将其设置到table中
void lua_setfield(lua_State *L, int index, const char *k); //从栈中获取指定位置的table和value,并将其设置到table的指定字段中
void lua_rawseti(lua_State *L, int index, lua_Integer n); //从栈中获取指定位置的table和value,并将其设置到table的指定索引中