通过lua栈了解lua与c的交互
lua是如何执行的

其中分析、执行部分都是c语言实现的。
lua与c的关系
lua的虚拟机是用c语言实现的,换句话说一段lua指令最终在执行时都是当作c语言来执行的,lua的global表,函数调用栈也都是存在c语言中的一个叫lua_State的结构体中的。
举个例子,来看下lua中的加指令 OP_ADD a b c 是如何实现的:
lua在运行时,会在c语言中的一个叫luaV_excute的函数中不断执行翻译后的lua指令,OP_ADD就是其中的一条指令(luaV_excute函数太长了,所以只在这里截取OP_ADD的部分,有兴趣可以直接去看lua的源码)
case OP_ADD: {
arith_op(luai_numadd, TM_ADD);
continue;
}
相关的一些宏定义:
#define luai_numadd(a,b) ((a)+(b))
//运算操作,op是运算的宏定义(加法、减法、乘法等),tm是元方法对应的枚举
#define arith_op(op,tm) { \
//获取b,c的值
TValue *rb = RKB(i); \
TValue *rc = RKC(i); \
//判断是否是b,c数字
if (ttisnumber(rb) && ttisnumber(rc)) { \
//从b,c指向的TValue中将数字字段取出来
lua_Number nb = nvalue(rb), nc = nvalue(rc); \
//进行op运算后放入a中
setnvalue(ra, op(nb, nc)); \
} \
//如果不是数字,则尝试调用ra,rb的元方法
else \
Protect(Arith(L, ra, rb, rc, tm)); \
}
//判断一个Tvalue是否是数字
#define ttisnumber(o) (ttype(o) == LUA_TNUMBER)
#define ttype(o) ((o)->tt)
//根据b的类型获取b对应的TValue
#define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \
ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i))
可以看到,OP_ADD其实就是把b,c(b,c是需要进行加运算的两个数字在函数常量中表中的位置中或调用栈中的位置上的TValue)的值加到了a中。
也就是说,每个lua指令最终的实现还是在通过执行c语言语句实现的。
lua中进行函数调用时栈的状态
lua和c的交互完全通过栈来进行交互,为了了解lua与c的交互一定要先了解lua的栈。
我们常说的lua栈有两种:
1.一个总的数据栈:每个lua环境唯一的栈,所有的调用信息都存在这上面。
2.每个函数的调用栈:函数的调用栈,其实并不是一个独立的栈,只是数据栈上的一小段。
与栈相关的结构体
lua_State
struct lua_State {
CommonHeader;
lu_byte status;
StkId top; /* first free slot in the stack */
StkId base; /* base of current function */
global_State *l_G;
CallInfo *ci; /* call info for current function */
const Instruction *savedpc; /* `savedpc' of current function */
StkId stack_last; /* last free slot in the stack */
StkId stack; /* stack base */
CallInfo *end_ci; /* points after end of ci array*/
CallInfo *base_ci; /* array of CallInfo's */
int stacksize;
int size_ci; /* size of array `base_ci' */
unsigned short nCcalls; /* number of nested C calls */
lu_byte hookmask;
lu_byte allowhook;
int basehookcount;
int hookcount;
lua_Hook hook;
TValue l_gt; /* table of globals */
TValue env; /* temporary place for environments */
GCObject *openupval; /* list of open upvalues in this stack */
GCObject *gclist;
struct lua_longjmp *errorJmp; /* current error recover point */
ptrdiff_t errfunc; /* current error handling function (stack index) */
};
lua_State保存lua运行相关所有信息的结构体,就是lua的运行环境,lua的栈和栈相关的信息都存在这个结构体里。这里说明几个和lua栈紧密相关的几个变量:
stack: lua栈的实体,在每个lua环境中是唯一的,每个函数的调用栈只是stack上的一小段。
top,base: 指向当前调用栈的栈顶和栈底。
ci: 当前函数的调用信息,具体结构下面会讲到。
base_ci: 函数调用信息的列表,用来记录和恢复当前的调用信息。
CallInfo
/*
** informations about a call
*/
typedef struct CallInfo {
StkId base; /* base for this function */
StkId func; /* function index in the stack */
StkId top; /* top for this function */
const Instruction *savedpc;
int nresults; /* expected number of results from this function */
int tailcalls; /* number of tail calls lost under this entry */
} CallInfo;
CallInfo是每一次函数调用的调用信息,这里也说明几个和栈紧密相关的变量。
base: 当前调用栈的栈底
top: 当前调用栈的栈顶
func: 当前调用的函数在stack中的位置
nresults: 当前函数预期会返回的结果个数,如果返回的数量不够,会用nil补齐
通过一个例子说明一下
我们这里在c语言中操作lua来调用c写的函数进行举例,因为用c语言去操作lua的流程更接近lua编译成指令后的过程,看的更清晰一些,又不至于像直接阅读虚拟机指令那么吃力。
使用的代码
供lua调用的c函数库(addlib.c):
#include <stdio.h>
#include <lua5.1/lua.h>
#include <lua5.1/lualib.h>
#include <lua5.1/lauxlib.h>
static int addc(lua_State *L)
{
//输出一下当前调用栈的元素个数
printf("get top in addc: %d\n",lua_gettop(L));
int a,b,c;
a = lua_tonumber(L,-1);
b = lua_tonumber(L,-2);
c = a + b;
//压入结果
lua_pushnumber(L,c);
//输出压入结果后的调用栈的元素个数
printf("get top in addc,after push result: %d\n",lua_gettop(L));
return 1;
}
static const struct luaL_Reg lib[] =
{
{"addc",addc},
{NULL,NULL}
};
int luaopen_addlib(lua_State *L)
{
luaL_register(L,"testadd",lib);
return 1;
}
调用代码:
#include <stdio.h>
#include <lua5.1/lua.h>
#include <lua5.1/lualib.h>
#include <lua5.1/lauxlib.h>
int main()
{
lua_State* luaEnv = lua_open();
//载入基础库
luaopen_base(luaEnv);
luaL_openlibs(luaEnv);
//输出一下载入库之后的栈中元素个数,lua_gettop(luaEnv)输出luaEnv->top - luaEnv->base,也就是当前调用栈中元素的个数
printf("get top after openlibs: %d\n",lua_gettop(luaEnv));
//载入addlib库
lua_getglobal(luaEnv,"require");
lua_pushstring(luaEnv,"addlib");
lua_pcall(luaEnv,1,0,0);
//输出载入addlib库后的栈中元素个数
printf("get top after require addlib: %d\n",lua_gettop(luaEnv));
//压入需要调用的函数和参数
lua_getglobal(luaEnv,"testadd");
lua_getfield(luaEnv,-1,"addc");
lua_pushinteger(luaEnv,10);
lua_pushinteger(luaEnv,12);
//输出压入后的栈中元素个数
printf("get top after push function and args: %d\n",lua_gettop(luaEnv));
//调用addc函数
lua_pcall(luaEnv,2,1,0);
//输出调用后的栈中元素的个数
printf("get top after pcall addc: %d\n",lua_gettop(luaEnv));
int result = lua_tonumber(luaEnv,-1);
//输出结果
printf("addc's result is : %d\n",result);
return 0;
}
调用结果:

过程说明
在c语言中调用一个lua函数的流程:

栈的详细变化过程
1.通过lua_open创建lua_State,并进行栈的初始化,栈的初始化操作在state_init()函数中。在state_init()中,创建栈的实例和函数调用列表实例,并设置了初始的ci信息,和初始ci的调用栈信息。此时栈是空的,base,top都指向栈的第一个元素。

2.打开基本的库后的栈(打开基本库后,栈中会剩余两张表格,但不影响之后的流程,所以先无视掉),因为在打开基本库的时候载入了两张表,所有top增加了2。

3.载入addlib库,用于函数调用(因为调用了返回0个返回值的pcall,所以对栈的内容没有影响,后面说),栈的状态完全没变。
4.压入要调用的函数及参数,准备进行调用。

5.通过lua_pcall(真正对addc函数的调用是在luaD_precall中)调用addc函数,在luaD_precall函数中会创建新的ci来用于保存addc的调用信息,将base移动到func+1的位置,作为新的ci的栈底。top不动,这样新的调用栈中就有addc的参数信息了。

6.在addc中读取调用栈中的两个参数,计算出结果,并压入栈中。

7.在addc调用结束后,lua会调用luaD_poscall回归到上一层,在回归时lua会根据return n的个数将addc调用栈栈顶的n个元素拷贝到,从addc位置开始的nresults个位置中,若n < nresults,少的部分补nil,并重新计算上一层栈顶。

小结:
①lua用于调用,交互的栈只有一个,每次进行函数调用时并不会新开一个栈来存储调用信息,而是新创建一个ci用于保存被调用函数的信息,并根据被调用函数的位置、参数个数将stack上的一段作为新的ci的调用栈,并将当前的调用信息(当前的函数位置,当前使用的栈的区间等信息)保存为一个callinfo,用于调用后的恢复。
②每次在lua中调用一个c函数时,会以函数在栈中的位置加1作为被调用函数callinfo的base,top位置保持不变作为新的调用栈的栈顶(调用lua函数略有不同,调用lua函数会将函数的参数新复制一份,但原理跟调用c函数差不多,所以不多做说明)。
③一次函数调用结束后,栈会根据之前保存的callinfo恢复栈的状态,并将函数调用的结果复制到当前栈顶的位置。
lua与c的交互
c调用lua函数
我们写一个接收一个参数,返回两个结果的PrintHello函数供c语言调用。
被调用的lua函数(PrintHello.lua):
--接收一个参数,返回两个结果的函数
function PrintHello(name)
--输出Hello
print("Hello "..name);
--给要返回的两个结果复制
result1 = "the name : "..name;
result2 = "something else...";
--返回结果
return result1,result2;
end
测试用c代码:
# include <lua5.1/lua.h>
# include <lua5.1/lualib.h>
# include <lua5.1/lauxlib.h>
int main()
{
//创建lua运行环境
lua_State* luaEnv = lua_open();
//打开基础库
luaopen_base(luaEnv);
luaL_openlibs(luaEnv);
//载入PrintHello.lua
luaL_loadfile(luaEnv,"PrintHello.lua");
//执行PrintHello.lua,将PrintHello加入Global表中
lua_pcall(luaEnv,0,0,0);
//将PrintHello函数、参数压栈,准备调用
lua_getglobal(luaEnv,"PrintHello");
lua_pushstring(luaEnv,"bard");
//调用PrintHello函数
lua_pcall(luaEnv,1,2,0);
//取出返回的两个结果并输出
char* result1 = lua_tostring(luaEnv,-2);
printf("%s\n",result1);
char* result2 = lua_tostring(luaEnv,-1);
printf("%s\n",result2);
return 1;
}
调用结果:

小结:
①没有看到PrintHello函数有显式从栈中取参数和压入结果的操作,那么取参数和将结果压栈的操作是在哪里进行的:
取参数: 在luaD_precall函数中,会创建新的ci供PrintHello使用,同时将栈顶的1个参数PrintHello的调用栈中,然后luaV_execute执行对应的PrintHello对应的一段指令,这些指令中会把name参数对应到调用栈的第一个位置去,这样就可以使用这个参数了。
压栈: PrintHello中的return会被翻译成两个OP_MOVE指令和一条OP_RETURN指令,进行结果的压栈和栈的恢复。
②其他操作就和前面说明调用栈的操作差不多了,这里就不做重复的解释。
lua调用c函数
我们写一个接收一个参数,返回两个结果的函数
供lua调用的c代码(testlib.c):
#include <lua5.1/lua.h>
#include <lua5.1/lualib.h>
#include <lua5.1/lauxlib.h>
#include <stdio.h>
static int printHelloInC(lua_State* L)
{
//取出栈中的参数
//如果想实现函数重载,可以对栈中的参数数量和参数类型进行判断后进行分别处理,这里只是为了展示lua对c的调用,
//所以没有进行参数检查
char* arg0 = lua_tostring(L,-1);
//输出Hello
printf("Hello %s\n",arg0);
//压入两个要返回的值
char* result1 = "this is result1";
lua_pushstring(L,result1);
char* result2 = "this is result2";
lua_pushstring(L,result2);
//表示这个函数有2个返回值
return 2;
}
static const struct luaL_Reg lib[] =
{
{"printHelloInC",printHelloInC},
{NULL,NULL}
};
int luaopen_testlib(lua_State *L)
{
luaL_register(L,"testlib",lib);
return 1;
}
lua测试代码:
--载入testlib库
require "testlib"
--调用testlib.printHelloInC函数
a,b = testlib.printHelloInC("bard")
--输出返回的两个结果
print(a)
print(b)
调用结果:

小结:
①同样的在lua中调用函数时,翻译的时候也会把一个函数的调用分成参数的压栈和函数调用两个部分,所以在c函数中取参数的时候可以直接取到。
②其他部分也和说明调用栈的变化过程中的例子差不多,也不做重复的解释了。
通过lua栈了解lua与c的交互的更多相关文章
- lua栈
既然Lua虚拟机模拟的是CPU的运作,那么Lua栈模拟的就是内存的角色.在Lua内部,参数的传递是通过Lua栈,同时Lua与C等外部进行交互的时候也是使用的栈.,先关注的是Lua栈的分配,管理和相关的 ...
- Lua 栈中元素的位置
Lua与C.C#等的交互是通过栈来实现的,每次插入元素都是放在栈顶(top),至于元素的index,可以使用正数和负数两种方式, 如取栈底开始至第index个元素 -index = gettop - ...
- lua和C++交互的lua栈操作——以LuaTinker为例
一. -- C++类注册函数(LuaTinker) 的lua栈操作: -- lua栈内容(执行到pop语句) 栈地址 <--执行语句 space_name[name] = t1 -- (2b8) ...
- Lua 架构 The Lua Architecture
转载自:http://magicpanda.net/2010/10/lua%E6%9E%B6%E6%9E%84%E6%96%87%E6%A1%A3/ Lua架构文档(翻译) 十 102010 前段时间 ...
- VC和VS调用Lua设置以及Lua C API使用。
通过c++调用lua 脚本, 环境VC++6.0 lua sdk 5.1.4 在调用前先认识几个函数.1.调用lua_open()将创建一个指向Lua解释器的指针.2. luaL_ope ...
- lua编程之lua与C相互调用
lua是扩展性非常良好的语言,虽然核心非常精简,但是用户可以依靠lua库来实现大部分工作.除此之外,lua还可以通过与C函数相互调用来扩展程序功能.在C中嵌入lua脚本既可以让用户在不重新编译代码的情 ...
- 高速掌握Lua 5.3 —— Lua与C之间的交互概览
Q:什么是Lua的虚拟栈? A:C与Lua之间通信关键内容在于一个虚拟的栈.差点儿全部的调用都是对栈上的值进行操作,全部C与Lua之间的数据交换也都通过这个栈来完毕.另外,你也能够使用栈来保存暂时变量 ...
- vJine 第三波 之 Lua 来袭 vJine.Lua
vJine.Lua vJine.Lua是Lua语言的C#封装库,可实现通过C#直接运行Lua脚本并与Lua脚本交互的功能. 1. 授权: MPL2.0 相关资源: nuget:(https://www ...
- lua调用不同lua文件中的函数
a.lua和b.lua在同一个目录下 a.lua调用b.lua中的test方法,注意b中test的写法 _M 和 a中调用方法: b.lua local _M = {}function _M.test ...
随机推荐
- CRM 数据查重
2.8 小工具 · 纷享销客产品手册https://www.fxiaoke.com/mob/guide/crmdoc/src/2-8%E5%B0%8F%E5%B7%A5%E5%85%B7.html C ...
- Ubuntu 服务器指南
https://help.ubuntu.com/lts/serverguide/ Jabber Instant Messaging Server https://help.ubuntu.com/l ...
- PHP MySql增删改查
mysql_connect()连接数据库 mysql_select_db选择数据库 mysql_fetch_assoc()获取结果集 mysql_query()执行sql语句 实例如下: <?p ...
- [转载]Linux目录说明
原作者博客: http://blog.51cto.com/yangrong/1288072 将文字部分转移到自己的目录下便于学习记录 感谢~ 2./目录 目录 描述 / 第一层次结构的根.整个文件系统 ...
- [转帖]Tensor是神马?为什么还会Flow?
Tensor是神马?为什么还会Flow? 互联网爱好者 百家号17-05-2310:03 大数据文摘作品,转载要求见文末 编译 | 邵胖胖,江凡,笪洁琼,Aileen 也许你已经下载了TensorFl ...
- SpringBoot(十五)_springboot实现预览pdf
最近,项目上要做个打印的东西,还要预览.我想就直接生成pdf预览,然后用户选择打印 于是,昨天找了找资料.一般用itext 进行转pdf.于是我就用springboot试了试,代码比较简单,现在只是简 ...
- 《ERP系统原理与实施》
第一 采购 第二 生产(生产任务->生产准备->加工单->派工单->生产调度->生产监控->数据采集->统计分析) 第三 仓储 第四 质量 第五 财务 第六 ...
- CSS实现水平垂直同时居中的6种思路
前面的话 水平居中和垂直居中已经单独介绍过,本文将介绍水平垂直同时居中的6种思路 水平对齐+行高 [思路一]text-align + line-height实现单行文本水平垂直居中 <style ...
- Sql Server 之 Merge
转载:http://blog.csdn.net/zmoneyz/article/details/38404111 现在我们来创建了两个表Person和UserLogin Person表如下: User ...
- 自学Aruba5.1-Aruba 基于角色(role)的策略管理(重点)
点击返回:自学Aruba之路 自学Aruba5.1-Aruba 基于角色(role)的策略管理(重点) 1. 角色Role介绍 在ArubaOS中,用户(User)指的是已经完成连接,并获取到IP地址 ...