第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于更高版本,但有一些区别。
第四版针对 Lua 5.3,可以在 Amazon 和其他书店买到。
购买本书,您还可以帮助支持 Lua 项目


25.3 – 通用调用函数

作为一个更高级的示例,我们将构建一个用于调用 Lua 函数的包装器,使用 C 中的 vararg 工具。我们的包装器函数(我们称之为 call_va)接收要调用的函数的名称、描述参数和结果类型的字符串、然后是参数列表,最后是用于存储结果的变量指针列表;它处理 API 的所有详细信息。使用此函数,我们可以简单地将我们之前的示例编写为

    call_va("f", "dd>d", x, y, &z);
其中字符串 "dd>d" 表示“两个 double 类型的参数,一个 double 类型的结果”。此描述符可以使用字母 `d´ 表示 double,`i´ 表示整数,`s´ 表示字符串;`>´ 将参数与结果分隔开。如果函数没有结果,则 `>´ 是可选的。
    #include <stdarg.h>
    
    void call_va (const char *func, const char *sig, ...) {
      va_list vl;
      int narg, nres;  /* number of arguments and results */
    
      va_start(vl, sig);
      lua_getglobal(L, func);  /* get function */
    
      /* push arguments */
      narg = 0;
      while (*sig) {  /* push arguments */
        switch (*sig++) {
    
          case 'd':  /* double argument */
            lua_pushnumber(L, va_arg(vl, double));
            break;
    
          case 'i':  /* int argument */
            lua_pushnumber(L, va_arg(vl, int));
            break;
    
          case 's':  /* string argument */
            lua_pushstring(L, va_arg(vl, char *));
            break;
    
          case '>':
            goto endwhile;
    
          default:
            error(L, "invalid option (%c)", *(sig - 1));
        }
        narg++;
        luaL_checkstack(L, 1, "too many arguments");
      } endwhile:
    
      /* do the call */
      nres = strlen(sig);  /* number of expected results */
      if (lua_pcall(L, narg, nres, 0) != 0)  /* do the call */
        error(L, "error running function `%s': %s",
                 func, lua_tostring(L, -1));
    
      /* retrieve results */
      nres = -nres;  /* stack index of first result */
      while (*sig) {  /* get results */
        switch (*sig++) {
    
          case 'd':  /* double result */
            if (!lua_isnumber(L, nres))
              error(L, "wrong result type");
            *va_arg(vl, double *) = lua_tonumber(L, nres);
            break;
    
          case 'i':  /* int result */
            if (!lua_isnumber(L, nres))
              error(L, "wrong result type");
            *va_arg(vl, int *) = (int)lua_tonumber(L, nres);
            break;
    
          case 's':  /* string result */
            if (!lua_isstring(L, nres))
              error(L, "wrong result type");
            *va_arg(vl, const char **) = lua_tostring(L, nres);
            break;
    
          default:
            error(L, "invalid option (%c)", *(sig - 1));
        }
        nres++;
      }
      va_end(vl);
    }
尽管具有通用性,但此函数遵循我们之前示例中的相同步骤:它推送函数,推送参数,执行调用,并获取结果。它的大部分代码都很直接,但有一些细微差别。首先,它不需要检查 func 是否是一个函数;lua_pcall 将触发任何偶发错误。其次,因为它推送任意数量的参数,所以它必须检查堆栈空间。第三,因为函数可能会返回字符串,所以 call_va 无法从堆栈中弹出结果。在完成使用偶发字符串结果(或将其复制到其他缓冲区)后,由调用者弹出它们。