经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
ua5.4源码剖析:三. C++与Lua相互调用
来源:cnblogs  作者:寡人正在Coding  时间:2023/2/1 10:27:49  对本文有异议

概述

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

示例

来看下面这段代码:
C++

  1. #include "Inc/lua.h"
  2. #include "Inc/lauxlib.h"
  3. #include "Inc/lualib.h"
  4. #include "Inc/lobject.h"
  5. }
  6. using std::cout;
  7. using std::endl;
  8. int CAdd(lua_State* L)
  9. {
  10. int a = lua_tonumber(L, 2);
  11. int b = lua_tonumber(L, 1);;
  12. int sum = a + b;
  13. lua_pushnumber(L, sum);
  14. return 1;
  15. }
  16. int main()
  17. {
  18. lua_State* L = luaL_newstate();
  19. luaL_openlibs(L);
  20. lua_register(L, "CAdd", CAdd);
  21. int stat = luaL_loadfile(L, "Test.lua") | lua_pcall(L, 0, 0, 0);
  22. if (stat)
  23. {
  24. cout << "error" << endl;
  25. }
  26. else
  27. {
  28. cout << "succ" << endl;
  29. }
  30. lua_close(L);
  31. return 0;
  32. }

lua

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

运行结果:

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

C++调用lua

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

  1. static void f_parser (lua_State *L, void *ud) {
  2. LClosure *cl;
  3. struct SParser *p = cast(struct SParser *, ud);
  4. int c = zgetc(p->z); /* read first character */
  5. if (c == LUA_SIGNATURE[0]) {
  6. checkmode(L, p->mode, "binary");
  7. cl = luaU_undump(L, p->z, p->name);
  8. }
  9. else {
  10. checkmode(L, p->mode, "text");
  11. cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c);
  12. }
  13. lua_assert(cl->nupvalues == cl->p->sizeupvalues);
  14. luaF_initupvals(L, cl);
  15. }

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

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

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

Proto

  1. typedef struct Proto {
  2. CommonHeader;
  3. lu_byte numparams; /* number of fixed parameters */
  4. lu_byte is_vararg;
  5. lu_byte maxstacksize; /* number of registers needed by this function */
  6. int sizeupvalues; /* size of 'upvalues' */
  7. int sizek; /* size of 'k' */
  8. int sizecode;
  9. int sizelineinfo;
  10. int sizep; /* size of 'p' */
  11. int sizelocvars;
  12. int linedefined; /* debug information */
  13. int lastlinedefined; /* debug information */
  14. TValue *k; /* constants used by the function */
  15. Instruction *code; //codes
  16. struct Proto **p; /* functions defined inside the function */
  17. int *lineinfo; /* map from opcodes to source lines (debug information) */
  18. LocVar *locvars; /* information about local variables (debug information) */
  19. Upvaldesc *upvalues; /* upvalue information */
  20. struct LClosure *cache; /* last-created closure with this prototype */
  21. TString *source; /* used for debug information */
  22. GCObject *gclist;
  23. } Proto;

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

FuncState

  1. typedef struct FuncState {
  2. Proto *f; /* current function header */
  3. struct FuncState *prev; /* enclosing function */
  4. struct LexState *ls; /* lexical state */
  5. struct BlockCnt *bl; /* chain of current blocks */
  6. int pc; /* next position to code (equivalent to 'ncode') */
  7. int lasttarget; /* 'label' of last 'jump label' */
  8. int jpc; /* list of pending jumps to 'pc' */
  9. int nk; /* number of elements in 'k' */
  10. int np; /* number of elements in 'p' */
  11. int firstlocal; /* index of first local var (in Dyndata array) */
  12. short nlocvars; /* number of elements in 'f->locvars' */
  13. lu_byte nactvar; /* number of active local variables */
  14. lu_byte nups; /* number of upvalues */
  15. lu_byte freereg; /* first free register */
  16. } FuncState;

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

编译lua流程

以加载lua脚本为例。

  1. f_parser调用luaY_parser分析,并初始化Upvalues(外局部变量)。
  2. luaY_parser 使用LexState包裹FuncState调用luaX_next进行进一步分析,其结果保存到Proto结构的code数组中,传递给LClosure并推入栈中。
  3. luaX_next循环分析,依据词法,语法规则调用luaK_code生成字节码。
    部分代码:
  1. static void statement (LexState *ls) {
  2. int line = ls->linenumber; /* may be needed for error messages */
  3. enterlevel(ls);
  4. switch (ls->t.token) {
  5. case ';': { /* stat -> ';' (empty statement) */
  6. luaX_next(ls); /* skip ';' */
  7. break;
  8. }
  9. case TK_IF: { /* stat -> ifstat */
  10. ifstat(ls, line);
  11. break;
  12. }
  13. //.....................
  14. }
  15. }

运行

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

  1. void luaD_call (lua_State *L, StkId func, int nResults) {
  2. if (++L->nCcalls >= LUAI_MAXCCALLS)
  3. stackerror(L);
  4. if (!luaD_precall(L, func, nResults)) /* is a Lua function? */
  5. luaV_execute(L); /* call it */
  6. L->nCcalls--;
  7. }
  8. }

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

  1. void luaV_execute (lua_State *L) {
  2. CallInfo *ci = L->ci;
  3. LClosure *cl;
  4. TValue *k;
  5. StkId base;
  6. ci->callstatus |= CIST_FRESH; /* fresh invocation of 'luaV_execute" */
  7. newframe: /* reentry point when frame changes (call/return) */
  8. lua_assert(ci == L->ci);
  9. cl = clLvalue(ci->func); /* local reference to function's closure */
  10. k = cl->p->k; /* local reference to function's constant table */
  11. base = ci->u.l.base; /* local copy of function's base */
  12. /* main loop of interpreter */
  13. for (;;) {
  14. Instruction i;
  15. StkId ra;
  16. vmfetch();
  17. vmdispatch (GET_OPCODE(i)) {
  18. vmcase(OP_MOVE) {
  19. setobjs2s(L, ra, RB(i));
  20. vmbreak;
  21. }
  22. //............................
  23. }
  24. }

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

  1. typedef struct CallInfo {
  2. StkId func; /* function index in the stack */
  3. StkId top; /* top for this function */
  4. struct CallInfo *previous, *next; /* dynamic call link */
  5. union {
  6. struct { /* only for Lua functions */
  7. StkId base; /* base for this function */
  8. const Instruction *savedpc;
  9. } l;
  10. struct { /* only for C functions */
  11. lua_KFunction k; /* continuation in case of yields */
  12. ptrdiff_t old_errfunc;
  13. lua_KContext ctx; /* context info. in case of yields */
  14. } c;
  15. } u;
  16. ptrdiff_t extra;
  17. short nresults; /* expected number of results from this function */
  18. unsigned short callstatus;
  19. } CallInfo;

lua调用C++

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

  1. #define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)
  2. #define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
  3. LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {
  4. lua_lock(L);
  5. if (n == 0) {
  6. setfvalue(s2v(L->top), fn);
  7. api_incr_top(L);
  8. }
  9. else {
  10. CClosure *cl;
  11. api_checknelems(L, n);
  12. api_check(L, n <= MAXUPVAL, "upvalue index too large");
  13. cl = luaF_newCclosure(L, n);
  14. cl->f = fn;
  15. L->top -= n;
  16. while (n--) {
  17. setobj2n(L, &cl->upvalue[n], s2v(L->top + n));
  18. /* does not need barrier because closure is white */
  19. }
  20. setclCvalue(L, s2v(L->top), cl);
  21. api_incr_top(L);
  22. luaC_checkGC(L);
  23. }
  24. lua_unlock(L);
  25. }

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

  1. typedef int (*lua_CFunction) (lua_State *L);
  2. typedef struct CClosure {
  3. ClosureHeader;
  4. lua_CFunction f;
  5. TValue upvalue[1]; /* list of upvalues */
  6. } CClosure;

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

解释运行调用语义

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

  1. void luaV_execute (lua_State *L, CallInfo *ci) {
  2. //...
  3. vmcase(OP_CALL) {
  4. int b = GETARG_B(i);
  5. int nresults = GETARG_C(i) - 1;
  6. if (b != 0) /* fixed number of arguments? */
  7. L->top = ra + b; /* top signals number of arguments */
  8. /* else previous instruction set top */
  9. ProtectNT(luaD_call(L, ra, nresults));
  10. vmbreak;
  11. }
  12. //...
  13. }

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

  1. void luaD_call (lua_State *L, StkId func, int nresults) {
  2. lua_CFunction f;
  3. retry:
  4. switch (ttypetag(s2v(func))) {
  5. case LUA_VCCL: /* C closure */
  6. f = clCvalue(s2v(func))->f;
  7. goto Cfunc;
  8. case LUA_VLCF: /* light C function */
  9. f = fvalue(s2v(func));
  10. Cfunc: {
  11. int n; /* number of returns */
  12. CallInfo *ci = next_ci(L);
  13. checkstackp(L, LUA_MINSTACK, func); /* ensure minimum stack size */
  14. ci->nresults = nresults;
  15. ci->callstatus = CIST_C;
  16. ci->top = L->top + LUA_MINSTACK;
  17. ci->func = func;
  18. L->ci = ci;
  19. lua_assert(ci->top <= L->stack_last);
  20. if (L->hookmask & LUA_MASKCALL) {
  21. int narg = cast_int(L->top - func) - 1;
  22. luaD_hook(L, LUA_HOOKCALL, -1, 1, narg);
  23. }
  24. lua_unlock(L);
  25. n = (*f)(L); /* do the actual call */
  26. lua_lock(L);
  27. api_checknelems(L, n);
  28. luaD_poscall(L, ci, n);
  29. break;
  30. }
  31. //...

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

原文链接:https://www.cnblogs.com/hggzhang/p/17079700.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号