Lua调用C函数的实现

        看Lua有一段时间了,说实话进展挺慢的。归根到底是因为“动机不纯”,我确实不是抱着学Lua的心态去看资料的。本来看Lua就是听说Lua的实现比较简单,可以借Lua理解解释性语言的一些细节实现。当然Lua也确实不简单的,闭包什么的概念以前都没听说过。没用过Lua却去硬啃Lua语言实现,我也是蛮拼的!
        我主要的参考资料是云风大神的《Lua源码赏析》和高手翻译的《LUA中文教程》。这里我想记录一下函数调用的过程。按照函数调用这条线串联一下各各知识点。首先要从字节码开始了。Lua虚拟机的指令集中有两条用于函数调用的字节码。
OP_CALL,/*	A B C	R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */
OP_TAILCALL,/*	A B C	return R(A)(R(A+1), ... ,R(A+B-1))		*/
        后一个适用于尾调用的情况,第一个字节码适用于一般情况。我们知道Lua代码的执行其实就是一个大大的for循环里面套一个大大的switch语句。当虚拟机读到了OP_CALL字节码后,就会调用对应的分支。对应的代码段如下:
    vmcase(OP_CALL,
        int b = GETARG_B(i);
        int nresults = GETARG_C(i) - 1;
        if (b != 0) L->top = ra+b;  /* else previous instruction set top */
        if (luaD_precall(L, ra, nresults)) {  /* C function? */
          if (nresults >= 0) L->top = ci->top;  /* adjust results */
          base = ci->u.l.base;
        }
        else {  /* Lua function */
          ci = L->ci;
          ci->callstatus |= CIST_REENTRY;
          goto newframe;  /* restart luaV_execute over new Lua function */
        }
      )
         Lua既可以调用Lua函数,也可以调用C函数。我先看看调用C函数吧!重点是luaD_precall(L, ra, nresults))。
int luaD_precall (lua_State *L, StkId func, int nresults) {
  lua_CFunction f;
  CallInfo *ci;
  int n;  /* number of arguments (Lua) or returns (C) */
  ptrdiff_t funcr = savestack(L, func);
  switch (ttype(func)) {
    case LUA_TLCF:  /* light C function */
      f = fvalue(func);
      goto Cfunc;
    case LUA_TCCL: {  /* C closure */
      f = clCvalue(func)->f;
     Cfunc:
      luaD_checkstack(L, LUA_MINSTACK);  /* ensure minimum stack size */
      ci = next_ci(L);  /* now 'enter' new function */
      ci->nresults = nresults;
      ci->func = restorestack(L, funcr);
      ci->top = L->top + LUA_MINSTACK;
      lua_assert(ci->top <= L->stack_last);
      ci->callstatus = 0;
      luaC_checkGC(L);  /* stack grow uses memory */
      if (L->hookmask & LUA_MASKCALL)
        luaD_hook(L, LUA_HOOKCALL, -1);
      lua_unlock(L);
      n = (*f)(L);  /* do the actual call */
      lua_lock(L);
      api_checknelems(L, n);
      luaD_poscall(L, L->top - n);
      return 1;
    }
		.........
  }
}

        ptrdiff_t funcr = savestack(L, func);   ci->func = restorestack(L, funcr); 这两句执行完之后,得到的结果就是ci->func = func。n = (*f)(L);通过这一句,实际的C函数就被调用了。 一个典型的lua调用的C函数如下:

static int l_sin (lua_State *L) {
		double d = lua_tonumber(L, 1); /* get argument */
		lua_pushnumber(L, sin(d)); /* push result */
		return 1; /* number of results */
}
        这个函数的第一行的作用是获取参数,lua_tonumber(L, 1)是一个宏。它实际调用了lua_tonumberx()。而在这个函数中通过间接的方式调用TValue *o = ci->func + idx;
获得了第idx个参数变量。Call指令明确规定函数参数存放在R(A)后的B-1个栈内。而ci->func就是指向了R(A)。那么Lua是如何保证参数个数的正确的呢?玄机就在lua_tonumberx()所调用的index2addr()中。
 static TValue *index2addr (lua_State *L, int idx) {
    CallInfo *ci = L->ci;
    if (idx > 0) {
    TValue *o = ci->func + idx;
    api_check(L, idx <= ci->top - (ci->func + 1), "unacceptable index");
    if (o >= L->top) return NONVALIDVALUE;
    else return o;
    /* ..... */
  }
        这个函数会比较o和L->top的位置,如果大于或等于,就会返回错误。而L->top是在OP_CALL的分支语句里面就改写成了ra+b,也就是最后一个参数的下一个位置。这样的机制就保证了C函数不会把参数个数弄错了。然后是参数返回的问题了,虚拟机指令规定返回值应该在R(A), ... ,R(A+C-2)。Lua是如何实现这种规定的呢?首先看函数lua_pushnumber(),其定义如下:
 LUA_API void lua_pushnumber (lua_State *L, lua_Number n) {
  lua_lock(L);
  setnvalue(L->top, n);
  luai_checknum(L, L->top,
    luaG_runerror(L, "C API - attempt to push a signaling NaN"));
  api_incr_top(L);
  lua_unlock(L);
}
        这个函数的工作就是把n写到L->top所指向的栈空间,然后把L->top向高位移动一个位置。很明显这个存储位置是不符合Lua虚拟机规定的。所以需要luaD_poscall(L, L->top - n)来完成这个任务。这个n是C函数的返回值个数,所以L->top其实是指向了C函数的第一个返回值。
int luaD_poscall (lua_State *L, StkId firstResult) {
  StkId res;
  int wanted, i;
  CallInfo *ci = L->ci;
  if (L->hookmask & (LUA_MASKRET | LUA_MASKLINE)) {
    if (L->hookmask & LUA_MASKRET) {
      ptrdiff_t fr = savestack(L, firstResult);  /* hook may change stack */
      luaD_hook(L, LUA_HOOKRET, -1);
      firstResult = restorestack(L, fr);
    }
    L->oldpc = ci->previous->u.l.savedpc;  /* 'oldpc' for caller function */
  }
  res = ci->func;  /* res == final position of 1st result */
  wanted = ci->nresults;
  L->ci = ci = ci->previous;  /* back to caller */
  /* move results to correct place */
  for (i = wanted; i != 0 && firstResult < L->top; i--)
    setobjs2s(L, res++, firstResult++);
  while (i-- > 0)
    setnilvalue(res++);
  L->top = res;
  return (wanted - LUA_MULTRET);  /* 0 iff wanted == LUA_MULTRET */
}
        其实luaD_poscall也比较简单,它其实就是一个返回值的“搬运工”。res被赋值为ci->func,实际上就指向了前面的R(A)。然后一个for循环实现了拷贝的工作。对应C函数的ci结构也就功成身退了。到此,一个C函数的调用过程就完成了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值