Lua与C如何实现交互

今天开始看Lua源码,因为项目组用的版本是5.1.4,所以我现在看的也是5.1.4版本的,看懂了以后再看5.2 那些地方有不同就OK了~~ ^_^

先从Lua的栈看起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 CallInfos */
int stacksize;
int size_ci; /* size of array `base_ci */
unsigned short nCcalls; /* number of nested C calls */
unsigned short baseCcalls; /* nested C calls when resuming coroutine */
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 调用C函数过程:

每当Lua调用一个C函数的时候,先查找到这这个函数所在的地址,然后将函数及参数全部顺序压入栈中,L->Top设置为最后一个参数的位置,然后调用luaD_precall,将L->Base移至函数的下一个位置,然后通过函数指针地址调用这个C函数。

C函数如何将返回值告诉Lua:

  1. 数字:比较简单,直接将结果压入栈顶。

  2. 字符串:这里要先说下Lua的字符串处理,Lua是将所有的字符串通过哈希后存入一个字符串的库中。所以在压入之前会先做哈希,然后查找是否有这个字符串,如果有的话将这个字符串的指针地址压入栈顶,没有则会去申请内存,根据哈希值记录下这个字符串(过程中还有一些垃圾回收的处理,暂略过),然后将新申请的这个内存地址压入栈顶。

  3. 表:表要稍微复杂一点,自己写了段代码。

1
2
3
4
5
6
7
lua_newtable(L); // 先新建一个表,压入栈顶
lua_pushstring(L, "x"); // push 一个Key至栈顶
lua_pushnumber(L, x1); // push 一个Value至栈顶
lua_settable(L, -3); // 将key和value取出来,将它们塞入表中
lua_pushstring(L, "y");
lua_pushnumber(L, y1);
lua_settable(L, -3); //栈顶的元素即为要压入的表

这里就要分析下lua_settable里头到底做了什么,核心就是2句话

1
2
luaV_settable(L, t, top - 2, top - 1);
top -= 2;

接下来再具体看看luaV_settable这个里头究竟又做了什么,核心代码是以下几句

1
2
3
4
5
6
7
8
9
TValue *oldval = luaH_set(L, h, key); /* do a primitive set */

if (!ttisnil(oldval) || /* result is no nil? */
(tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL)
{ /* or no TM? */
setobj2t(L, oldval, val);
luaC_barriert(L, h, val);
return;
}

再看luaH_set:

1
2
3
4
5
6
7
8
9
10
11
const TValue *p = luaH_get(t, key);
flags = 0;
if (p != luaO_nilobject)
return cast(TValue *, p);
else
{
if (ttisnil(key)) luaG_runerror(L, "table index is nil");
else if (ttisnumber(key) luai_numisnan(nvalue(key)));
luaG_runerror(L, "table index is NaN");
return newkey(L, t, key);
}

原来是先Get一下这个key对应的值,如果找到了,则直接返回,否则new一个出来返回(个人觉得luaH_set这个名字取的不太好,明明是get的意思= =)

那好了,基本上整个流程就很清楚了。

扩展延伸:那Lua是如何去New一个key的呢?跟进newkey这个函数。

大概就是先找一个空闲的内存,找到了则分配(每个表内部有一个内存管理器),没有则重新哈希一次表(会向系统申请新的内存),然后将key对应的结点的地址返回(具体表的实现还没仔细看,下次再专门看看表的实现吧)

===============分割线=================

Lua 调用C函数过程:(待续)


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!