Lua 学习 chapter29

6.6k 词

目录

  1. 前言
  2. c函数
  3. 延续
  4. c模块

生活总需要一点仪式感,然后慢慢的像那个趋向完美的自己靠近。

前言

Lua调用c函数时,我们必须注册该函数,即不需以一种恰当的方式为lua提供该c函数的地址。

lua调用c函数时,也使用了一个与c语言调用Lua函数时相同类型的栈,c函数从栈中获取参数,并将结果压入到栈中。

需要注意的是,这个栈不是一个全局结构;每个函数都有其私有的局部栈。当Lua调用一个c函数时,第一个参数总是位于这个局部栈中,索引为1的位置。即使一个c函数调用了lua点吗,而且lua代码用再次调用了同一个的c函数,这些调用每次只会看到本次调用自己的私有栈,其中索引为1的位置就是第一个参数。

C函数

随便写一个c的函数,该函数求一个数的正选值。

1
2
3
4
5
6
static int l_sin(lua_State *L)
{
	double d = lua_tonumber(L,1);//获取参数
	lua_pushnumber(L,sin(d));//将结果入栈
	return 1;//返回值的个数
}

所有在lua中注册的函数都必须使用一个相同的原型,该原型就是定义在lua.h中的lua_CFunction:

1
2
typedef int(*lua_CFunction) (lua_State *L)

从C语言角度看,这个函数只有一个指向lua状态类型的指针作为参数,返回值为一个整型数,代表压入栈中的返回值的个数。因此,该函数在压入结果前无需清空栈。在该函数返回后,lua会自动报错返回值并清空整个栈。

在lua中,调用这个函数前,还需要通过lua_pushfunction注册该函数。函数lua_pushfunction会获取一个指向c函数的指针,然后再lua中创建一个“function”类型,代表注册的函数。一旦完成注册,c函数就可以像其他lua函数一样行事了。

一种快速测试l_sin的方法是,将其代码放到简单解释器中,并将线面的代码添加到luaL_openlibs调用的后面:

1
2
lua_pushfunction(L,l_sin);
lua_setglobal(L,"mysin")

延续

通过lua_pcall和lua.call,一个被lua调用的c函数也可以回调lua函数。标准库中有一些函数就是这么做的:table.sort调用了排序函数,string.gsub调用了替换含糊,pcall和xpcall以保护模式来调用函数。如果你还记得lua代码本身就是被c代码(宿主)调用的,那么你应该知道调用顺序类似于:C(宿主)调用Lua(脚本),lua(脚本)又调用了C(库),C(库)又调用了lua(回调).

一般lua语言可以处理这种调用顺序,,但是处理协程的话会有问题。

lua语言中,每个协程都有自己的栈,其中保存了改协程所挂起调用的信息。具体的说,急救室该栈中存储了每一个调用的返回地址,参数以及局部变量。对于lua函数的调用,解释器只需要这个栈即可,我们将其称为软栈。然而对于c函数的调用,解释器必须使用c语言栈。毕竟c函数的返回地址和局部变量都位于c语言栈中。

对于解释器来说,拥有多个软栈并不难,但是ISO C的运行环境只能拥有一个内部栈。因此lua中的协程无法保存c函数的执行:如果一个c函数位于从resume到对应yield的调用路径中,那么lua无法保存c函数的状态以便下次resume时恢复状态。

1
2
3
4
5
6
co = corourine.wrap(function()
	print(pcall(corourine.yield))
	end)
co()

–>false attempt to yield across metamethod/C-call boundary

函数pcall是一个c语言函数;因此,lua5.1不能将其挂起,因为ISO C无法挂起一个C函数并在之后恢复期运行。

在lua5.2版本中,用延续来改善对这个问题的处理。lua5.2使用长跳转实现了yield,并使用相同的方式实现了错误处理。长跳转简单的抛弃可以指定一个延续函数foo_K,该函数也是一个c函数,在要恢复goo的执行时他就会被调用。也就是说,当解释器发现它应该恢复函数foo的执行时,如果长跳转已经丢弃了c语言栈中有关foo的信息,则调用foo_k来代替。

1
2
3
4
5
6
7
8
9
10
static int luaB_pcall(lua_State *L)
{
	int status;
	lua_checkany(L,1);//至少一个参数
	status = lua_pcall(L,lua_gettop(L) - 1, LUA_MULTRET, 0 );//
	lua_pushboolean(L,(status==LUA_OK));//状态
	lua_insert(L,1); //状态是第一个结果
	return lua_gettop(L); //返回状态和所有结果
}

如果程序正在通过lua_pcall被调用的函数yield,那么后面就不可能恢复luaB_pcall的执行。因此,如果我们在保护模式的调用下试图yield时,解释器就会抛出异常。lua5.3使用的基本类似于下面的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static int finishpcall(lua_State *L, int status, intptr_t ctx)
{
	(void) ctx;
	status = (status != LUA_OK && status != LUA_YIELD);
	lua_pushboolean(L, (status == 0));
	lua_insert(L,1);
	return lua_gettop(L);
}

static int luaB_pcall(lua_State *L)
{
int status;
lua_checkany(L,1);//至少一个参数
status = lua_pcall(L,lua_gettop(L) - 1, LUA_MULTRET, 0, 0,finishpcall);//
return finishpcall(L,status,0);
}

c模块

lua模块就是一个代码段,其中定义了一下lua函数并将其存储在恰当的地方(通常是表中的元素).为lua编写的c语言模块可以模仿这种行为。除了c函数的定义外,c模块还必须定义一个特殊函数,这个特殊函数相当于lua库中的主代码段,用于注册模板中的所有c函数,并将太闷存储在恰当的地方(通常也是表中的元素)。与lua的主代码段一样,这个函数还应该初始化模块中所有需要初始化的其他东西。

lua通过注册过程感知到c函数,一旦一个c函数用lua表示和存储,lua就会通过对其地址(就是我们注册函数时提供的lua的信息)的直接引用来调用它。换句话说,一旦一个c函数完成注册,lua调用它试就不在依赖于其函数名、包的位置以及可见性规则。通常,一个c模块中只有一个用于打开库的公共函数;其他的所有函数都是私有的,在c语言中被声明为static。

当我们使用c函数扩展lua程序时,将代码设计为一个c模块是一个不错的想法。因为即使我们现在只想注册一个函数,但迟早总会需要其他的函数。通常,辅助库为这项工作提供了一个辅助函数。宏luaL_newlib接收一个由c函数及其对应函数名组成的数组,并将这下函数注册到一个新表中。eg:假设我们要用一个之前顶一个函数l_dir创建一个库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int l_dir(lua_State *L)
{
	DIR *dir;
	struct dirent *entry;
	int i;
	const char *path = luaL_checkstring(L,1);
	dir = opendir(path);
	if (dir == NULL)
	{
		lua_pushnil(L);
		lua_pushstring(L,strerror(errno));
		return 2;
	}
<span class="n">lua_newtable</span><span class="p">(</span><span class="n">L</span><span class="p">);</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">while</span><span class="p">((</span><span class="n">entry</span> <span class="o">=</span> <span class="n">readdir</span><span class="p">(</span><span class="n">dir</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">lua_pushinteger</span><span class="p">(</span><span class="n">L</span><span class="p">,</span><span class="n">i</span><span class="o">++</span><span class="p">);</span>
	<span class="n">lua_pushstring</span><span class="p">(</span><span class="n">L</span><span class="p">,</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">);</span>
	<span class="n">lua_settable</span><span class="p">(</span><span class="n">L</span><span class="p">,</span><span class="o">-</span><span class="mi">3</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">closedir</span><span class="p">(</span><span class="n">dir</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>

}

然后,声明一个数组,这个数组包含了模块中所有的函数及其名称。数组元素的类型为luaL_Reg,该类型是由两个字段组成的结构体,这个两个字段分别是函数名和函数指针。

1
2
3
4
5
static const struct luaL_Reg mylib[] = {
	{"dir", l_dir},
	{NULL,NULL} //哨兵

}

上面的例子只声明了一个函数,数组的最后一个元素永远是{NULL,NULL},并以此表示数组的结尾。最后我们声明一个函数luaL_newlib声明一个主函数:

1
2
3
4
5
int luaopen_mylib(lua_State *L)
{
	luaL_newlib(L, mylib);
	return 1;
}

对函数lua_newLib的调用会创建一个表,并使用由数组mylib指定的”函数名-函数指针”填充这个新创建的表,当luaL_newlib返回时,它把这个新创建的表留在栈中,在表中它打开了这个库。然后,函数luaopne_mylib返回1,表示再将这个表返回给lua。

然后我们将其链接到解释器中,我们用代码创建一个动态链接库,并将这个库放到c语言路径中的某个地方,之后就可以require在乱终直接加载这个模块了。

1
local mylib = require "mylib"

上述的语句会将动态库mylib链接到lua,查找函数luaopen_mylib,将其注册为一个c语言函数,探后调用它一打开模块。

动态链接器必须知道函数luaopen_mylib的名字才能找到他。它总是寻找名为”luaopne_+模块名”这样的函数。因此如果我们的模块名为mylib,那么该函数应该命名为luaopen_mylib。

如果解释器不支持动态链接,需要同新库一起重新编译lua语言。除了重新编译,还需要以某种方式告诉独立解释器,他应该再打开一个新状态时打开这个库。一个简单的做法是把luaopen_mylib添加到由luaLopenlibs打开的标准库列表中,这个列表位于文件linit.c中。

            <hr style="visibility: hidden;"/>
            
            <hr style="visibility: hidden;"/>