C++实现与Lua相互调用的示例详解

2023-05-13 20:05:42 示例 调用 详解

概述

从本质上来看,其实说是不存在所谓的C++与lua的相互调用。lua是运行在C上的,简单来说lua的代码会被编译成字节码在被C语言的语法运行。在C++调用lua时,其实是解释运行lua文件编译出来的字节码。lua调用C++其实还是解释运行lua文件编译出来的字节码的语义是调用lua栈上的C++函数。

示例

来看下面这段代码:

C++

#include "Inc/lua.h"
#include "Inc/lauxlib.h"
#include "Inc/lualib.h"
#include "Inc/lobject.h"
}

using std::cout;
using std::endl;

int CAdd(lua_State* L)
{
	int a = lua_tonumber(L, 2);
	int b = lua_tonumber(L, 1);;
	int sum = a + b;
	lua_pushnumber(L, sum);
	return 1;
}

int main()
{
	lua_State* L = luaL_newstate();
	luaL_openlibs(L);

	lua_reGISter(L, "CAdd", CAdd);

	int stat = luaL_loadfile(L, "Test.lua") | lua_pcall(L, 0, 0, 0);
	if (stat)
	{
		cout << "error" << endl;
	}
	else
	{
		cout << "succ" << endl;
	}

	lua_close(L);

	return 0;
}

lua

local x = CAdd(1, 2)
print("x = " .. tostring(x))

运行结果:

考虑上述C++代码luaL_loadfile去加载并调用lua,lua又调用了C++注册到lua虚拟机里的CAdd函数并正确打印了返回值,结果如图所示。到底发生了什么?

C++调用lua

C++调用lua时,是对lua代码进行编译生成字节码,在运行时对字节码使用C的语法解释运行。

对luaL_loadfile调试,跟到f_parser:

static void f_parser (lua_State *L, void *ud) {
  LClosure *cl;
  struct SParser *p = cast(struct SParser *, ud);
  int c = zgetc(p->z);  
  if (c == LUA_SIGNATURE[0]) {
    checkmode(L, p->mode, "binary");
    cl = luaU_undump(L, p->z, p->name);
  }
  else {
    checkmode(L, p->mode, "text");
    cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c);
  }
  lua_assert(cl->nupvalues == cl->p->sizeupvalues);
  luaF_initupvals(L, cl);
}

简单来说,parser根据输入进行词法,语法分析进行编码生成闭包,然后推入栈中等待调用。来看几个用到的数据结构

LClosure

typedef struct LClosure {
  ClosureHeader;
  struct Proto *p;
  UpVal *upvals[1];  //被捕获的外局部变量
} LClosure;

这是lua的闭包,此外还有CClosure是c的闭包,下面lua调用C++会提到,它们被Closure联合体包裹。

Proto

typedef struct Proto {
  CommonHeader;
  lu_byte numparams;  
  lu_byte is_vararg;
  lu_byte maxstacksize;  
  int sizeupvalues;  
  int sizek;  
  int sizecode;
  int sizelineinfo;
  int sizep;  
  int sizelocvars;
  int linedefined;  
  int lastlinedefined;  
  TValue *k;  
  Instruction *code;  //codes
  struct Proto **p;  
  int *lineinfo;  
  LocVar *locvars;  
  Upvaldesc *upvalues;  
  struct LClosure *cache;  
  TString  *source;  
  GCObject *gclist;
} Proto;

Instruction *code;注意这个变量,这个变量就是指向我们编译后生成字节码数组的指针。

FuncState

typedef struct FuncState {
  Proto *f;  
  struct FuncState *prev;  
  struct LexState *ls;  
  struct BlockCnt *bl;  
  int pc;  
  int lasttarget;   
  int jpc;  
  int nk;  
  int np;  
  int firstlocal;  
  short nlocvars;  
  lu_byte nactvar;  
  lu_byte nups;  
  lu_byte freereg;  
} FuncState;

FuncState互相是嵌套的,外部FuncState保存了内部的部分信息,最外部的FuncState的f成员保存了编译的所有字节码,并传递给闭包LClosure。

编译lua流程

以加载lua脚本为例。

  • f_parser调用luaY_parser分析,并初始化Upvalues(外局部变量)。
  • luaY_parser 使用LexState包裹FuncState调用luaX_next进行进一步分析,其结果保存到Proto结构的code数组中,传递给LClosure并推入栈中。
  • luaX_next循环分析,依据词法,语法规则调用luaK_code生成字节码。
    部分代码:
static void statement (LexState *ls) {
  int line = ls->linenumber;  
  enterlevel(ls);
  switch (ls->t.token) {
    case ';': {  
      luaX_next(ls);  
      break;
    }
    case TK_IF: {  
      ifstat(ls, line);
      break;
    }
    //.....................
 }
}

运行

编译代码后,便可对闭包进行解析运行了。调试代码上述 lua_pcall(L, 0, 0, 0) 代码,跟到luaD_call:

void luaD_call (lua_State *L, StkId func, int nResults) {
  if (++L->nCcalls >= LUAI_MAXCCALLS)
    stackerror(L);
  if (!luaD_precall(L, func, nResults))  
    luaV_execute(L);  
  L->nCcalls--;
}
}

首先调用luaD_precall进行预备工作,lua_state扩展base_ci(CallInfo类型)数组创建一个新元素保存括虚拟机的指令指针(lua_state->savedpc)在内的调用堆栈的状态以便调用结束后恢复调用堆栈,并把指令指针指向该闭包的指令数组(Closure->p->codes)。

然后调用luaV_execute循环取出指令运行。

luaV_execute解释执行部分代码:

void luaV_execute (lua_State *L) {
  CallInfo *ci = L->ci;
  LClosure *cl;
  TValue *k;
  StkId base;
  ci->callstatus |= CIST_FRESH;  
 newframe:  
  lua_assert(ci == L->ci);
  cl = clLvalue(ci->func);  
  k = cl->p->k;  
  base = ci->u.l.base;  
  
  for (;;) {
    Instruction i;
    StkId ra;
    vmfetch();
    vmdispatch (GET_OPCODE(i)) {
      vmcase(OP_MOVE) {
        setobjs2s(L, ra, RB(i));
        vmbreak;
      }
   	//............................
  }
}

CallInfo

函数执行时,lua_state通过CallInfo 数据结构了解函数的状态信息,并通过CallInfo组base_ci的上下生长来维护调用堆栈。

typedef struct CallInfo {
  StkId func;  
  StkId	top;  
  struct CallInfo *previous, *next;  
  uNIOn {
    struct {  
      StkId base;  
      const Instruction *savedpc;
    } l;
    struct {  
      lua_KFunction k;  
      ptrdiff_t old_errfunc;
      lua_KContext ctx;  
    } c;
  } u;
  ptrdiff_t extra;
  short nresults;  
  unsigned short callstatus;
} CallInfo;

lua调用C++

lua调用C++,是上述C++调用lua时即c的语法解释运行lua代码生成的字节码的一种情况,即取出lua状态机全局表中的CClosure中的函数指针运行。

来看下向lua状态机注册C++函数lua_register

#define lua_pushcfunction(L,f)	lua_pushcclosure(L, (f), 0)
#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))

LUA_api void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {
  lua_lock(L);
  if (n == 0) {
    setfvalue(s2v(L->top), fn);
    api_incr_top(L);
  }
  else {
    CClosure *cl;
    api_checknelems(L, n);
    api_check(L, n <= MAXUPVAL, "upvalue index too large");
    cl = luaF_newCclosure(L, n);
    cl->f = fn;
    L->top -= n;
    while (n--) {
      setobj2n(L, &cl->upvalue[n], s2v(L->top + n));
      
    }
    setclCvalue(L, s2v(L->top), cl);
    api_incr_top(L);
    luaC_checkGC(L);
  }
  lua_unlock(L);
}

可以看到这里最终创建了一个CCloseure,包裹住lua_CFunction类型的函数指针并推入栈顶和放入全局表中。

typedef int (*lua_CFunction) (lua_State *L);

typedef struct CClosure {
  ClosureHeader;
  lua_CFunction f;
  TValue upvalue[1];  
} CClosure;

可以看到CClosure包含了一个lua_CFunction类型的函数指针和upvalue的链表

解释运行调用语义

循环解释字节码语义的关于调用的部分

void luaV_execute (lua_State *L, CallInfo *ci) {
//...
vmcase(OP_CALL) {
        int b = GETARG_B(i);
        int nresults = GETARG_C(i) - 1;
        if (b != 0)  
          L->top = ra + b;  
        
        ProtectNT(luaD_call(L, ra, nresults));
        vmbreak;
      }
//...
}

可以看到调用语义的解释调用了luaD_call

void luaD_call (lua_State *L, StkId func, int nresults) {
  lua_CFunction f;
   retry:
  switch (ttypetag(s2v(func))) {
    case LUA_VCCL:  
      f = clCvalue(s2v(func))->f;
      Goto Cfunc;
    case LUA_VLCF:  
      f = fvalue(s2v(func));
     Cfunc: {
      int n;  
      CallInfo *ci = next_ci(L);
      checkstackp(L, LUA_MINSTACK, func);  
      ci->nresults = nresults;
      ci->callstatus = CIST_C;
      ci->top = L->top + LUA_MINSTACK;
      ci->func = func;
      L->ci = ci;
      lua_assert(ci->top <= L->stack_last);
      if (L->hookmask & LUA_MASKCALL) {
        int narg = cast_int(L->top - func) - 1;
        luaD_hook(L, LUA_HOOKCALL, -1, 1, narg);
      }
      lua_unlock(L);
      n = (*f)(L);  
      lua_lock(L);
      api_checknelems(L, n);
      luaD_poscall(L, ci, n);
      break;
    }
//...

可以看到这里取到了上述Closure中的函数指针并进行调用。

到此这篇关于C++实现与Lua相互调用的示例详解的文章就介绍到这了,更多相关C++ Lua相互调用内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关文章