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_TNILnil
LUA_TBOOLEANbool
LUA_TLIGHTUSERDATAuserdata(light)
LUA_TNUMBERnumber
LUA_TSTRINGstring
LUA_TTABLEtable
LUA_TFUNCTIONfunction
LUA_TUSERDATAuserdata(full)
LUA_TTHREADthread

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: double
  • lua_Integer: long long
  • lua_Unsigned: unsigned long long
  • lua_KContext: ptrdiff_t
  • lua_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]

ab的注释:

  • a: 出栈数
  • b: 入栈数
  • 特殊标记?: 入栈/出栈数未知
  • x/y: 入栈/出栈xy

c的注释:

  • -: 无异常
  • m: 可能抛出Out-of-memory异常
  • e: 可能抛出任何异常
  • v: 将故意抛出异常

函数将出栈a个值, 然后入栈b个值, 或者抛出异常.

3.3 参数记号

下面在函数中使用这几个记号

记号注释
LLua状态机
n字符串
i索引
T类型码(实际为int)
op算符码(实际为int)
cop比较算符码(实际为int)
bbool(实际为int, 1=true, 0=false)
ssize_t或int
cfC函数
kf协程函数
na参数数
nr返回值数
alloc内存分配器
udvoid*任意数据
^被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)udS.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)TS.push(_G.n)
[-1, +1, e]lua_gettable(L,i)TS.push(S[i][S[-1]])
[-0, +1, e]lua_getfield(L,i,n)TS.push(S[i][n])
或调用__index函数
[-0, +1, e]lua_geti(L,i,int)TS.push(S[i][int])
[-1, +1, e]lua_rawget(L,i)TS.push(S[i][S[-1]])
[-0, +1, e]lua_rawgetp(L,i,n)TS.push(S[i][n])
不调用__index函数
[-0, +1, e]lua_rawgeti(L,i,int)TS.push(S[i][int])
[-0, +(0/1), -]lua_getmetatable(L,i)b若S[i]有元表:
 S.push(S[i]的元表)
[-0, +1, -]lua_getuservalue(L,i)TS.push(getuservalue(S[i]))
获取S[i](限userdata)的uservalue
4.3.2 拉取函数(栈 -> C)
栈注释函数名参数返回值注释
[-0, +0, -]lua_toboolean(L,i)bS[i] != false && S[i] != nil
[-0, +0, -]lua_tocfunction(L,i)cf非Lua_CFunction返回NULL
[-0, +0, -]lua_tointeger(L,i)lua_IntegerS[i]为有效string/number
其他返回0
[-0, +0, -]lua_tointegerx(L,i,&isnum)Lua_IntegerS[i]为有效string/number
失败时返回0
[-0, +0, -]lua_tonumber(L,i)Lua_NumberS[i]为有效string/number
其他返回0
[-0, +0, -]lua_tonumberx(L,i,&isnum)Lua_NumberS[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)^nS.push(n)
复制n,返回内部字符串
[-0, +1, m]lua_pushstring(L,n)^nS.push(n)
复制n,返回内部字符串
[-0, +1, m]lua_pushliteral(L,n)^nS.push(n)
复制n,返回内部字符串
限字面量字符串用
[-0, +1, e]lua_pushfstring(L,n,…)^nS.push(格式化n),
返回内部字符串
[-0, +1, e]lua_pushvfstring(L,n,va_list)^nS.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)bS[i1] === S[i2]
不调用__eq
[-0, +0, e]lua_compare(L,i1,i2,cop)bS[i1] cop S[i2]
[-1, +0, v]lua_error(L)-用S.pop()构建异常并
做longjmp,永不返回
[-1, +(2/0), e]lua_next(L,i)bk, 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 魔术元函数

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐