背景及需求
lua作为一门发展成熟的脚本语言,正在变得越来越流行。它也可以作为和C/C++执行脚本交互的语言。并且Lua的整个库很小,当Lua自身带有的部分库无法满足我们自身需要实现的功能时,我们就需要引入其他人写的第三方c库来对已有的Lua功能进行扩展和优化。或者另一种情况,一些为了提高效率的功能。
这里我需要一个问题就是关于lua中产生伪随机数的问题。当我们用已有的random产生伪随机数的话,通常我们将时间设置为该函数的随机种子。但是这会有个问题,同样的随机种子在下一次产生的随机数是一样一样的。比如在一个for循环中想要产生很多随机数,因为for循环执行时间很短,导致随机种子也相同,出来的随机数会有很大可能会是相同的。那么就需要找到另外的更好的产生伪随机数的算法。比如梅森旋转算法,简介看这里点击打开链接 ,而在lua的第三方库中已经有同学实现了它,叫lrandom点击打开链接.如果你想要在自己本地测试一下这个lrandom库的话,可以选用一个很方便的Lua 的模块安装和部署工具 - LuaRocks,很开心这个的使用方法有同学详细描述了它点击打开链接 .当以上做过之后,你仅仅可以在自己本地来使用这个模块,而无法在自己工程中使用。
将第三方c库添加到工程中(lua调用c/c++)
那么先以lmathlib.c为例,这个数学库。
static const luaL_Reg mathlib[] = {
{"abs", math_abs},
{"acos", math_acos},
{"asin", math_asin},
{"atan2", math_atan2},
{"atan", math_atan},
{"ceil", math_ceil},
{"cosh", math_cosh},
{"cos", math_cos},
{"deg", math_deg},
{"exp", math_exp},
{"floor", math_floor},
{"fmod", math_fmod},
{"frexp", math_frexp},
{"ldexp", math_ldexp},
#if defined(LUA_COMPAT_LOG10)
{"log10", math_log10},
#endif
{"log", math_log},
{"max", math_max},
{"min", math_min},
{"modf", math_modf},
{"pow", math_pow},
{"rad", math_rad},
{"random", math_random},
{"randomseed", math_randomseed},
{"sinh", math_sinh},
{"sin", math_sin},
{"sqrt", math_sqrt},
{"tanh", math_tanh},
{"tan", math_tan},
{NULL, NULL}
};
这里是关于所有的这个库中需要使用的函数的数组。将luaL_Reg这个结构体展开便可得知这个数组中每个元素的含义了。
<strong><span style="font-size:18px;">typedef struct luaL_Reg {
const char *name;
lua_CFunction func;
} luaL_Reg;</span></strong>
再将lua_CFunction展开看下
<strong><span style="font-size:18px;">typedef int (*lua_CFunction) (lua_State *L);</span></strong>
(这个是定义在lua.h中的,这个头文件中还同时定义了其他函数指针,)这个就定义了所有的用这种方式的函数的定义规则比如看下求sin值。
<strong><span style="font-size:18px;">static int math_sin (lua_State *L) {
lua_pushnumber(L, l_tg(sin)(luaL_checknumber(L, 1)));
return 1;
}</span></strong>
在lua中传入的参数是如何传给c代码的那?都是通过类似的函数LuaL_checkXXXX来从栈中取得。将返回值Push到栈中,函数返回值是返回值的个数。
而大量的luaL_XXX这种命名规则,是代表此函数不是LUA的核心代码。
再来看这个.c文件中的另一段代码
<strong><span style="font-size:18px;">/*
** Open math library
*/
LUAMOD_API int luaopen_math (lua_State *L) {
luaL_newlib(L, mathlib);
lua_pushnumber(L, PI);
lua_setfield(L, -2, "pi");
lua_pushnumber(L, HUGE_VAL);
lua_setfield(L, -2, "huge");
return 1;
}</span></strong>
这个是将该函数作为一个模块放在一个table中。里面有函数地址等等。LUAMOD_API就是extern 供其他文件直接引用.
我们要加入的lrandom库实际跟这个数学库结构是一样的。
另一个重要文件linit.c
<strong><span style="font-size:18px;">/*
** these libs are loaded by lua.c and are readily available to any Lua
** program
*/
static const luaL_Reg loadedlibs[] = {
{"_G", luaopen_base},
{LUA_LOADLIBNAME, luaopen_package},
{LUA_COLIBNAME, luaopen_coroutine},
{LUA_TABLIBNAME, luaopen_table},
{LUA_IOLIBNAME, luaopen_io},
{LUA_OSLIBNAME, luaopen_os},
{LUA_STRLIBNAME, luaopen_string},
{LUA_BITLIBNAME, luaopen_bit32},
{LUA_MATHLIBNAME, luaopen_math},
{LUA_DBLIBNAME, luaopen_debug},
{LUA_RANLIBNAME, luaopen_random},
{NULL, NULL}
};
/*
** these libs are preloaded and must be required before used
*/
static const luaL_Reg preloadedlibs[] = {
{NULL, NULL}
};</span></strong>
这两个数组放在一起看。首先loadLibs LuaL_Reg是之前看到的结构体。以math库为例:
<strong><span style="font-size:18px;">#define LUA_MATHLIBNAME "math"
LUAMOD_API int (luaopen_math) (lua_State *L);</span></strong>
该宏定义在lualib.h中 而这里的名字就是我们要再lua中使用的模块名。 下面是用来外链函数的。刚才看到的以luaopen为前缀的到处API.
<strong><span style="font-size:18px;">LUALIB_API void luaL_openlibs (lua_State *L) {
const luaL_Reg *lib;
/* call open functions from 'loadedlibs' and set results to global table */
for (lib = loadedlibs; lib->func; lib++) {
luaL_requiref(L, lib->name, lib->func, 1);
lua_pop(L, 1); /* remove lib */
}
/* add open functions from 'preloadedlibs' into 'package.preload' table */
luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD");
for (lib = preloadedlibs; lib->func; lib++) {
lua_pushcfunction(L, lib->func);
lua_setfield(L, -2, lib->name);
}
lua_pop(L, 1); /* remove _PRELOAD table */
}</span></strong>
Opens all standard Lua libraries into the given state将类库都加载到虚拟机中。这里分了两部分,分别用到了上面两个数组。可以看出perloadedLibs是在程序刚起来的时候就将模块进行预定义,在lua中需要用到的时候进行require,而loadedlibs则对相应模块进行加载,而不需要在lua中再进行require而直接使用。
从这里的循环也可以看出为什么之前数组中都是以NULL结尾的。
另一个问题是require是怎么work的。由于自己水平有限只能了解它的功能,它会根据模块名去查找这个table,然后判断是否已经加载过了该文件,找到后加载该模块。源码在loadlib.c中得 ll_require,具体的技术细节,可以参考下面仁兄的讲解:点击打开链接 ,
这里只是简单地介绍了操作步骤和部分原理。这位同学提供了个完整的例子来提供参考。点击打开链接
而具体整体的一个技术细节,觉得这位同学写的很详尽。点击打开链接