[Lua] Lua与C交互入门

[Lua] Lua与C交互入门

文章目录

  • [Lua] Lua与C交互入门
    • 枚举值
      • 状态码
      • 类型码
      • 算符码
      • 比较算符码
    • 交互类型
    • 概念
      • 函数注释
      • 参数记号
    • 函数
      • 状态机
        • 构造和析构
        • 状态信息
      • 栈操作
        • 基本栈操作
        • 值操作
        • 建值
      • 交换函数
        • 拉取函数(Lua -> 栈)
        • 拉取函数(栈 -> C)
          • 探测函数
        • 推送函数(C -> 栈)
        • 推送函数(栈 -> Lua)
      • 算符函数
      • 调用函数
        • lua_call(L,na,nr)调用协议 (C -> Lua)
        • lua_pcall(L,na,nr,i)调用协议 (C -> Lua)
        • lua_CFunction 调用协议 (Lua -> C)
      • 协程函数
        • 协程协议
          • 启动协程
          • 恢复协程
          • 协程启动函数
          • 挂起协程
        • 流程过程例子
        • 协程例子之生成器 (使用lua_KContext)
      • 闭包支持

枚举值

状态码

枚举名 状态
LUA_OK 正常
LUA_YIELD 挂起中
LUA_ERRRUN 运行错误
LUA_ERRSYNTAX 语法错误
LUA_ERRMEM 内存错误
LUA_ERRGCMM 内存回收错误
LUA_ERRERR 错误系统错误
LUA_ERRFILE 文件错误(扩展)

类型码

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

算符码

枚举名 算符
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 >>

比较算符码

枚举名 算符
LUA_OPEQ =
LUA_OPLT <
LUA_OPLE <=

交互类型

  • 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)

概念

初始化后栈内无内容.

可用索引存取栈中值, 设栈中有n个值, 则1引用栈底值, n引用栈顶值, -1引用栈顶值, -n引用栈底值. 其他值是非法值.

函数注释

与Lua的交互通过栈进行, 每个函数都有入栈出栈和异常的栈注释.

  • 格式: [-a, +b, c]

ab的注释:

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

c的注释:

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

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

参数记号

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

记号 注释
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() 表示出栈.

函数

状态机

构造和析构

栈注释 函数名 参数 返回值 注释
[-0, +0, -] lua_newstate (alloc, ud) L
[-0, +0, -] luaL_newstate () L
[-0, +0, -] lua_close (L)
[-0, +1, m] lua_newthread (L) L 建立共享变量的独立栈

状态信息

栈注释 函数名 参数 返回值 注释
[-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(恢复协程)时可恢复

栈操作

基本栈操作

栈注释 函数名 参数 返回值 注释
[-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 检查栈是否还有
足够空间容纳s个值
L1 [-s, +0, -]
L2 [-0, +s, -]
lua_xmove (L1,L2,s) L2.push(L1.pop())
…(s次)
L2.push(L1.pop())
跨栈移值
[-n, +0, -] lua_pop (L,s) 出栈s个

值操作

栈注释 函数名 参数 返回值 注释
[-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
不支持伪栈索引

建值

栈注释 函数名 参数 返回值 注释
[-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管理

交换函数

跨域快速交换函数 (C -> Lua)

栈注释 函数名 参数 返回值 注释
[-0, +0, e] lua_register (L,n,cf) _G.n = cf

拉取函数(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]有meta表:
 S.push(S[i]的meta表)
[-0, +1, -] lua_getuservalue (L,i) T S.push(getuservalue(S[i]))
获取S[i](限userdata)的uservalue

拉取函数(栈 -> 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

推送函数(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)

推送函数(栈 -> 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()
[-1, +0, -] lua_setuservalue (L,i) setuservalue(S[i], S.pop())

算符函数

栈注释 函数名 参数 返回值 注释
[-(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

调用函数

栈注释 函数名 参数 返回值 注释
[-(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不能为伪栈索引
见调用协议

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]

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, -].

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()然后抛出异常, 该函数不返回

协程函数

栈注释 函数名 参数 返回值 注释
L1 [-?, +?, -]
L2 [-?, +?, -]
lua_resume (L1,L2,na,&nr) 状态码 将L2作为协程栈恢复协程
返回运行结束后协程的状态

协程协议

启动协程

启动协程需要一个普通的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.

未完待续

流程过程例子

[Lua] Lua与C交互入门_第1张图片

协程例子之生成器 (使用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

闭包支持

栈注释 函数名 参数 返回值 注释
[-0, +0, -] lua_upvalueindex (i) i 闭包栈索引转伪栈索引

未完待续

你可能感兴趣的:([Lua] Lua与C交互入门)