[Lua] Lua与C交互入门
[Lua] Lua与C交互入门文章目录[Lua] Lua与C交互入门枚举值状态码类型码算符码比较算符码交互类型概念栈函数注释参数记号函数状态机构造和析构状态信息栈操作基本栈操作值操作建值交换函数拉取函数(Lua -> 栈)拉取函数(栈 -> C)探测函数推送函数(C -> 栈)推送函数(栈 -> Lua)算符函数调用函数lua_call(L,na,nr)调用协议 (C -&
Lua与C交互入门
文章目录
1 枚举值
1.1 状态码
| 枚举名 | 状态 |
|---|---|
| LUA_OK | 正常 |
| LUA_YIELD | 挂起中 |
| LUA_ERRRUN | 运行错误 |
| LUA_ERRSYNTAX | 语法错误 |
| LUA_ERRMEM | 内存错误 |
| LUA_ERRGCMM | 内存回收错误 |
| LUA_ERRERR | 错误系统错误 |
| LUA_ERRFILE | 文件错误(扩展) |
1.2 类型码
Lua共8种类型
| 枚举名 | Lua类型 |
|---|---|
| LUA_TNIL | nil |
| LUA_TBOOLEAN | bool |
| LUA_TLIGHTUSERDATA | userdata(light) |
| LUA_TNUMBER | number |
| LUA_TSTRING | string |
| LUA_TTABLE | table |
| LUA_TFUNCTION | function |
| LUA_TUSERDATA | userdata(full) |
| LUA_TTHREAD | thread |
1.3 算符码
| 枚举名 | 算符 |
|---|---|
| LUA_OPADD | + |
| LUA_OPSUB | - |
| LUA_OPMUL | * |
| LUA_OPDIV | / |
| LUA_OPIDIV | // |
| LUA_OPMOD | % |
| LUA_OPPOW | ^ |
| LUA_OPUNM | - |
| LUA_OPBNOT | ~ |
| LUA_OPBAND | & |
| LUA_OPBOR | | |
| LUA_OPBXOR | ~ |
| LUA_OPSHL | << |
| LUA_OPSHR | >> |
1.4 比较算符码
| 枚举名 | 算符 |
|---|---|
| LUA_OPEQ | = |
| LUA_OPLT | < |
| LUA_OPLE | <= |
2 交互类型
lua_Number:doublelua_Integer:long longlua_Unsigned:unsigned long longlua_KContext:ptrdiff_tlua_CFunction:int(*)(lua_State*)lua_KFunction:int(*)(lua_State*, int status, lua_KContext ctx)lua_Reader:const char*(*)(lua_State*, void*ud, size_t *sz)lua_Writer:int(*)(lua_State*, const void*p, size_t sz, void*ud)lua_Alloc:void*(void*ud, void*ptr, size_t osize, size_t nsize)
3 概念
3.1 栈
初始化后栈内无内容.
可用索引存取栈中值, 设栈中有n个值, 则1引用栈底值, n引用栈顶值, -1引用栈顶值, -n引用栈底值. 其他值是非法值.
3.2 函数注释
与Lua的交互通过栈进行, 每个函数都有入栈出栈和异常的栈注释.
- 格式:
[-a, +b, c]
a和b的注释:
a: 出栈数b: 入栈数- 特殊标记
?: 入栈/出栈数未知 x/y: 入栈/出栈x或y个
c的注释:
-: 无异常m: 可能抛出Out-of-memory异常e: 可能抛出任何异常v: 将故意抛出异常
函数将出栈a个值, 然后入栈b个值, 或者抛出异常.
3.3 参数记号
下面在函数中使用这几个记号
| 记号 | 注释 |
|---|---|
| L | Lua状态机 |
| n | 字符串 |
| i | 索引 |
| T | 类型码(实际为int) |
| op | 算符码(实际为int) |
| cop | 比较算符码(实际为int) |
| b | bool(实际为int, 1=true, 0=false) |
| s | size_t或int |
| cf | C函数 |
| kf | 协程函数 |
| na | 参数数 |
| nr | 返回值数 |
| alloc | 内存分配器 |
| ud | void*任意数据 |
| ^ | 被Lua托管的值, 需尽快使用以免被Lua释放 |
| &* | 指针 |
函数注释中用 S 表示栈, U 表示闭包栈.
S[1] 表示栈底, S[-1] 表示栈顶. S.push(v) 表示入栈, S.pop() 表示出栈.
4 函数
4.1 状态机
4.1.1 构造和析构
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-0, +0, -] | lua_newstate | (alloc, ud) | L | |
| [-0, +0, -] | luaL_newstate | () | L | |
| [-0, +0, -] | lua_close | (L) | ||
| [-0, +1, m] | lua_newthread | (L) | L | 建立共享变量的独立栈 |
4.1.2 状态信息
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-0, +0, -] | lua_status | (L) | 状态码 | |
| [-0, +0, -] | lua_version | (L) | *lua_Number | 版本号 |
| [-0, +0, m] | lua_gc | (L,gc,int) | int | 垃圾回收 |
仅LUA_OK时可调用函数, 仅LUA_OK(启动新协程)和LUA_YIELD(恢复协程)时可恢复
4.2 栈操作
4.2.1 基本栈操作
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-0, +0, -] | lua_absindex | (L,i) | i | 获取绝对索引 |
| [-0, +0, -] | lua_gettop | (L) | i | 栈顶值索引 栈中值数 0则栈空 |
| [-?, +?, -] | lua_settop | (L,i) | 设定栈中值数 变多则用nil填充 变少则删除值 0则删除所有值 |
|
| [-0, +1, -] | lua_pushvalue | (L,i) | S.push(S[i]) 复制 |
|
| [-0, +0, -] | lua_copy | (L,i1,i2) | S[i2] = S[i1] 复制替换 |
|
| [-0, +0, -] | lua_checkstack | (L,s) | b | 栈默认大小为LUA_MINSTACK=20 若栈大小不足s则试图增加大小 不足且增加失败则返回false |
| L1 [-s, +0, -] L2 [-0, +s, -] |
lua_xmove | (L1,L2,s) | L2.push(L1[-s]) …(s次) L2.push(L1[-1]) L1.pop(s) 跨栈移值 |
|
| [-n, +0, -] | lua_pop | (L,s) | 出栈s个 |
4.2.2 值操作
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-1, +0, -] | lua_replace | (L,i) | S[i] = S.pop() | |
| [-1, +0, -] | lua_remove | (L,i) | S[i] = S[i+1] S[i+1] = S[i+2] … S[-2] = S[-1] S.pop() 不支持伪栈索引 |
|
| [-1, +1, -] | lua_insert | (L,i) | t = S[-1] S[-1] = S[-2] S[-2] = S[-3] … S[i+1] = S[i] S[i] = t 不支持伪栈索引 |
4.2.3 建值
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-0, +1, m] | lua_createtable | (L,narr,nrec) | S.push({}) 建立长度narr, 容量nrec的表 |
|
| [-0, +1, m] | lua_newtable | (L) | S.push({}) | |
| [-0, +1, m] | lua_newuserdata | (L,s) | ud | S.push(malloc(s)) 内存由Lua管理 |
4.3 交换函数
跨域快速交换函数 (C -> Lua)
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-0, +0, e] | lua_register | (L,n,cf) | _G.n = cf |
4.3.1 拉取函数(Lua -> 栈)
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-0, +1, e] | lua_getglobal | (L,n) | T | S.push(_G.n) |
| [-1, +1, e] | lua_gettable | (L,i) | T | S.push(S[i][S[-1]]) |
| [-0, +1, e] | lua_getfield | (L,i,n) | T | S.push(S[i][n]) 或调用__index函数 |
| [-0, +1, e] | lua_geti | (L,i,int) | T | S.push(S[i][int]) |
| [-1, +1, e] | lua_rawget | (L,i) | T | S.push(S[i][S[-1]]) |
| [-0, +1, e] | lua_rawgetp | (L,i,n) | T | S.push(S[i][n]) 不调用__index函数 |
| [-0, +1, e] | lua_rawgeti | (L,i,int) | T | S.push(S[i][int]) |
| [-0, +(0/1), -] | lua_getmetatable | (L,i) | b | 若S[i]有元表: S.push(S[i]的元表) |
| [-0, +1, -] | lua_getuservalue | (L,i) | T | S.push(getuservalue(S[i])) 获取S[i](限userdata)的uservalue |
4.3.2 拉取函数(栈 -> C)
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-0, +0, -] | lua_toboolean | (L,i) | b | S[i] != false && S[i] != nil |
| [-0, +0, -] | lua_tocfunction | (L,i) | cf | 非Lua_CFunction返回NULL |
| [-0, +0, -] | lua_tointeger | (L,i) | lua_Integer | S[i]为有效string/number 其他返回0 |
| [-0, +0, -] | lua_tointegerx | (L,i,&isnum) | Lua_Integer | S[i]为有效string/number 失败时返回0 |
| [-0, +0, -] | lua_tonumber | (L,i) | Lua_Number | S[i]为有效string/number 其他返回0 |
| [-0, +0, -] | lua_tonumberx | (L,i,&isnum) | Lua_Number | S[i]为有效string/number 失败时返回0 |
| [-0, +0, m] | lua_tostring | (L,i) | ^n | 若S[i]不为number或string 则返回NULL |
| [-0, +0, m] | lua_tolstring | (L,i, &len) | ^n | 若S[i]不为number或string 则返回NULL |
| [-0, +0, -] | lua_tothread | (L,i) | L | |
| [-0, +0, -] | lua_touserdata | (L,i) | ud |
探测函数
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-0, +0, -] | lua_isnumber | (L,i) | b | |
| [-0, +0, -] | lua_isstring | (L,i) | b | |
| [-0, +0, -] | lua_iscfunction | (L,i) | b | 是否为C函数 |
| [-0, +0, -] | lua_isinteger | (L,i) | b | |
| [-0, +0, -] | lua_isuserdata | (L,i) | b | |
| [-0, +0, -] | lua_type | (L,i) | T | |
| [-0, +0, -] | lua_typename | (L,T) | n |
4.3.3 推送函数(C -> 栈)
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-0, +1, -] | lua_pushnil | (L) | S.push(nil) | |
| [-0, +1, -] | lua_pushnumber | (L,number) | S.push(number) | |
| [-0, +1, -] | lua_pushinteger | (L,int) | S.push(int) | |
| [-0, +1, m] | lua_pushlstring | (L,n,s) | ^n | S.push(n) 复制n,返回内部字符串 |
| [-0, +1, m] | lua_pushstring | (L,n) | ^n | S.push(n) 复制n,返回内部字符串 |
| [-0, +1, m] | lua_pushliteral | (L,n) | ^n | S.push(n) 复制n,返回内部字符串 限字面量字符串用 |
| [-0, +1, e] | lua_pushfstring | (L,n,…) | ^n | S.push(格式化n), 返回内部字符串 |
| [-0, +1, e] | lua_pushvfstring | (L,n,va_list) | ^n | S.push(格式化n), 返回内部字符串 |
| [-s, +1, m] | lua_pushcclosure | (L,cf,s) | 将S[-1]~S[-s]的值作为 cf的闭包值制作闭包 |
|
| [-0, +1, -] | lua_pushcfunction | (L,cf) | S.push(cf) | |
| [-0, +1, -] | lua_pushboolean | (L,b) | S.push(b) | |
| [-0, +1, -] | lua_pushlightuserdata | (L,ud) | S.push(ud) | |
| [-0, +1, -] | lua_pushthread | (L) | S.push(L) | |
| [-0, +1, -] | lua_pushglobaltable | (L) | S.push(_G) |
4.3.4 推送函数(栈 -> Lua)
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-1, +0, e] | lua_setglobal | (L, n) | _G.n = S.pop() | |
| [-2, +0, e] | lua_settable | (L,i) | S[i][S[-2]] = S[-1] S.pop(), S.pop() |
|
| [-1, +0, e] | lua_setfield | (L,i,n) | S[i][n] = S.pop() | |
| [-1, +0, e] | lua_seti | (L,i,int) | S[i][int] = S.pop() | |
| [-2, +0, e] | lua_rawset | (L,i) | S[i][S[-2]] = S[-1] S.pop(), S.pop() |
|
| [-1, +0, e] | lua_rawsetp | (L,i,n) | S[i][n] = S.pop() | |
| [-1, +0, e] | lua_rawseti | (L,i,int) | S[i][int] = S.pop() | |
| [-1, +0, -] | lua_setmetatable | (L,i) | S[i]的meta表 = S.pop() 只有userdata(非lightuserdata)和表可用 |
|
| [-1, +0, -] | lua_setuservalue | (L,i) | setuservalue(S[i], S.pop()) |
4.4 算符函数
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-(2/1), +1, e] | lua_arith | (L,op) | S.push(S.pop() op S.pop()) | |
| [-0, +0, -] | lua_rawequal | (L,i1,i2) | b | S[i1] === S[i2] 不调用__eq |
| [-0, +0, e] | lua_compare | (L,i1,i2,cop) | b | S[i1] cop S[i2] |
| [-1, +0, v] | lua_error | (L) | - | 用S.pop()构建异常并 做longjmp,永不返回 |
| [-1, +(2/0), e] | lua_next | (L,i) | b | k, v = S[i][++S.pop()] S.push(k) S.push(v) kv是S.pop()的下一键值对 |
| [-n, +1, e] | lua_concat | (L,s) | S.push(S.pop()…S.pop()… … …S.pop()) | |
| [-0, +1, e] | lua_len | (L,i) | S.push(#S[i]) 或触发length函数 |
|
| [-0, +1, -] | lua_stringtonumber | (L,n) | s | 字符串转数值,失败时返回0 |
4.5 调用函数
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-(na + 1), +(nr/?), e] | lua_call | (L,na,nr) | 见调用协议 | |
| [-(na + 1), +nr, e] | lua_callk | (L,na,nr, kctx,kf) |
||
| [-(na + 1), +(nr/1), -] | lua_pcall | (L,na,nr,i) | 状态码 | i不能为伪栈索引 见调用协议 |
| [-(na + 1), +(nr/1), -] | lua_pcallk | (L,na,nr,i, kctx,kf) |
状态码 | i不能为伪栈索引 见调用协议 |
4.5.1 lua_call(L,na,nr)调用协议 (C -> Lua)
有函数
function f(a1, a2, a3)
...
return r1, r2, r3
end
lua_call(L,na,nr) 按以下协议调用该函数, 栈注释 [-(na + 1), +nr, e], na=3, nr=3
S.push(f)
S.push(a1)
S.push(a2)
S.push(a3)
lua_call(L,na,nr)
r1 = S.pop()
r2 = S.pop()
r3 = S.pop()
nr 为LUA_MULTRET(-1)时, 按实际返回的参数入栈, 此时栈注释 [-(na + 1), +?, e]
4.5.2 lua_pcall(L,na,nr,i)调用协议 (C -> Lua)
S[i]应为 异常处理器 类同lua_call(L,na,nr), 栈注释 [-(na + 1), +(nr/?), -], 只在函数出现异常时有区别
函数调用出现异常时, 会将异常作为参数调用S[i], S[i]的返回值作为函数调用的返回值. 栈注释 [-(na + 1), +1, -].
4.5.3 lua_CFunction 调用协议 (Lua -> C)
lua_CFunction 类型为 int(*)(lua_State*L)
设Lua用该代码调用C函数
r1, r2, r3 = f(a1, a2, a3)
C函数的参数L的栈内数据为
- S[1]: a1
- S[2]: a2
- S[3]: a3
- lua_gettop(L): 3
返回时只需保证栈顶是需要返回的值, 然后C函数返回返回值的数量即可
- S[-3]: r1
- S[-2]: r2
- S[-1]: r3
- return 3;
或者抛出异常
- S[-1]: 异常参数
- lua_error(L); // 读取S.pop()然后抛出异常, 该函数不返回
4.6 协程函数
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| L1 [-?, +?, -] L2 [-?, +?, -] |
lua_resume | (L1,L2,na,&nr) | 状态码 | 将L2作为协程栈恢复协程 返回运行结束后协程的状态 |
4.6.1 协程协议
启动协程
启动协程需要一个普通的lua状态机(最好是空的), 所谓普通的lua状态机即 lua_status(L) 返回 LUA_OK 的状态机.
首先栈底压入 协程启动函数, 然后按顺序压入启动参数, 最后调用 lua_resume 启动协程.
co.S.push(cf)
co.S.push(a1)
co.S.push(a2)
co.S.push(a3)
lua_resume(co, NULL, 3) // 第二个参数可选 为协程的外部环境, 用于保存错误信息
协程第一次挂起之后, lua_resume的调用即返回, 返回值为co的状态码, co中只剩下协程第一次挂起时返回的值. 而 lua_status(co) 应返回 LUA_YIELD.
恢复协程
恢复协程需要一个挂起的Lua状态机. 所谓挂起的Lua状态机即 lua_status(co) 返回 LUA_YIELD 的状态机.
恢复时可选择发送任意值给协程.
将 co 清空, 然后将要发送的值压入栈即可.
lua_settop(co, 0);
co.S.push(re1)
co.S.push(re2)
co.S.push(re3)
lua_resume(co, NULL, -) // 第三个参数无效, 可为任意值
协程可能因挂起而退出, 也可能因返回而退出
挂起退出时, lua_resume 返回 LUA_YIELD, 返回退出时返回 LUA_OK.
返回 LUA_OK, 即说明co回归成一个普通的Lua状态机. 其栈中的值为返回值. 否则为挂起值.
协程启动函数
协程由一个lua_CFunction型的启动函数和多个lua_KFunction型的k函数组成.
启动函数中须使用lua_yieldk指定k函数做一次挂起, 否则该协程才刚创建即刻就销毁.
挂起协程
使用 lua_resume 启动协程后, 将马上调用启动函数, 启动函数内应调用 lua_yield, 否则该协程将弱化为普通的函数调用.
可使用 lua_yieldk(co, nr, ctx, kf) 中的kf指定下一次协程恢复时调用的恢复函数, Lua不对ctx有任何功能, Lua只负责在协程函数间传递ctx.
未完待续
4.6.2 流程过程例子

4.6.3 协程例子之生成器 (使用lua_KContext)
int RangeKFunc(lua_State* L, int state, lua_KContext kctx) {
switch(kctx) {
case 0:
EXPECT_EQ(lua_gettop(L), 2);
lua_pushvalue(L, 1);
if(lua_compare(L, 2, 3, LUA_OPEQ) == 0) {
lua_pushvalue(L, 3);
lua_yieldk(L, 1, 2, RangeKFunc);
} else {
return 0;
}
case 2:
EXPECT_EQ(lua_gettop(L), 3);
lua_pushinteger(L, 1);
lua_arith(L, LUA_OPADD);
case 1:
EXPECT_EQ(lua_gettop(L), 3);
if(lua_compare(L, 2, 3, LUA_OPEQ) == 0) {
lua_pushvalue(L, 3);
lua_yieldk(L, 1, 2, RangeKFunc);
} else {
return 0;
}
}
return 0;
}
int RangeEnter(lua_State* L) {
lua_yieldk(L, 0, 0, RangeKFunc);
return 0;
}
int RangeGene2(lua_State* L) {
lua_Integer a = luaL_checkinteger(L, 1);
lua_Integer b = luaL_checkinteger(L, 2);
lua_State* co = lua_newthread(L);
lua_pushcfunction(co, RangeEnter);
lua_pushinteger(co, a);
lua_pushinteger(co, b);
lua_resume(co, L, 2);
return 1;
}
使用如下代码产生协程对象, 并用C遍历生成器.
lua_pushcfunction(L, RangeGene2);
lua_pushinteger(L, 4);
lua_pushinteger(L, 7);
lua_call(L, 2, 1);
lua_State* co = lua_tothread(L, 1);
EXPECT_EQ(lua_status(co), LUA_YIELD);
EXPECT_EQ(lua_resume(co, L, 0), LUA_YIELD);
EXPECT_EQ(lua_gettop(co), 1);
EXPECT_EQ(luaL_checkinteger(co, 1), 4);
lua_settop(co, 0);
EXPECT_EQ(lua_resume(co, L, 0), LUA_YIELD);
EXPECT_EQ(lua_gettop(co), 1);
EXPECT_EQ(luaL_checkinteger(co, 1), 5);
lua_settop(co, 0);
EXPECT_EQ(lua_resume(co, L, 0), LUA_YIELD);
EXPECT_EQ(lua_gettop(co), 1);
EXPECT_EQ(luaL_checkinteger(co, 1), 6);
lua_settop(co, 0);
EXPECT_EQ(lua_resume(co, L, 0), LUA_OK);
EXPECT_EQ(lua_gettop(co), 0);
EXPECT_EQ(lua_status(co), LUA_OK);
lua_settop(L, 0);
亦可用如下Lua代码遍历生成器:
lua_pushcfunction(L, RangeGene2);
lua_pushinteger(L, 4);
lua_pushinteger(L, 7);
lua_call(L, 2, 1);
lua_setglobal(L, "r2");
const char* s =
"print(coroutine.resume(r2))"
"print(coroutine.resume(r2))"
"print(coroutine.resume(r2))"
"print(coroutine.resume(r2))"
"print(coroutine.resume(r2))"
;
luaL_dostring(L, s);
运行生成
true 4
true 5
true 6
true
false cannot resume dead coroutine
4.7 闭包支持
| 栈注释 | 函数名 | 参数 | 返回值 | 注释 |
|---|---|---|---|---|
| [-0, +0, -] | lua_upvalueindex | (i) | i | 闭包栈索引转伪栈索引 |
未完待续
5 魔术元函数
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)