通过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 ...
随机推荐
- Difference between prop and attr in different version of jquery
jQuery <1.9$('#inputId').attr('readonly', true); jQuery 1.9+$('#inputId').prop('readonly', true); ...
- yarn工具的使用
<!-- yarn init === npm init --> <!-- yarn login === npm adduser -->登录 <!-- yarn publi ...
- PDB自动启动以及Oracle Pfile的参数修改示范
1. Oracle12c 可以使用PDB的模式进行创建, 但是他一般不会自动启动,所以可以穿件一个触发器进行处理 创建语句 CREATE TRIGGER open_all_pdbs AFTER STA ...
- array与List之间相互转化
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/5/28 16:05 # @Author : zhang chao # @Fi ...
- spring学习总结(一)_Ioc基础(上)
最近经历了许许多多的事情,学习荒废了很久.自己的目标成了摆设.现在要奋起直追了.最近发现了张果的博客.应该是一个教师.看了他写的spring系列的博客,写的不错.于是本文的内容参考自他的博客,当然都是 ...
- Delphi的关键字
Constructor;构造器,定义构造函数使用Constructor关键字
- Delphi编程中动态菜单要点归纳
一.创建菜单并添加项目 在设计程序时,有时需要动态创建菜单, 通常使用以下的语句: PopupMenu1 := TPopupMenu.Create(Self); Item := TMenuIte ...
- day25 面向对象引子
面向对象编程所谓模子就是 类 抽象的,能知道什么属性,但是不知道属性具体值一切都是对象 有具体值 属性和技能都是根据类 模子来规范 # 人狗大战 # 角色模型 # 人的模型 def Person(na ...
- 【POI每日题解 #9】SKA-Piggy Banks
题目链接 题意: 有一棵环套树 求最少从多少个节点出发能沿边走过整棵树 环套树 并查集求联通块 有几块就砸几个 太简单不发代码了 不过某大佬的环套树找环dfs让我研究了好久… 贴一下以Orz #inc ...
- Python入门基础之循环
如果计算机不能循环,那么它比人还笨,实际上它也确实比人笨.你之所以觉得计算机好厉害,是因为它快,guangzhoushenbo.com计算机可以在1秒钟内重复做一件事情成千上万次. Python学习交 ...