在实际的项目中,往往不会只有一个.lua文件,而是有多个.lua文件一起在跑,这就需要宿主程序在开始运行的时候把所有需要用到的.lua文件全部加载,这一步通常是由require代为完成。
通常程序里第一个被加载的.lua文件会作为入口文件,由这个入口文件来require其他.lua文件,从而实现对所有.lua文件的加载。
lua官方提供了require、module函数来加载多个lua文件,这里就讲解require函数(module在后续的lua版本中已经被弃用,不做讲解)。
一个lua文件可以看作是一个模块,require函数的作用就是加载一个模块,使用require函数时,需要传入模块的名字,比如需要加载A.lua,即写成require "A"。为了更便捷地了解require函数,我们可以先去lua的官网下载lua的源代码,下载完毕解压后,能在里面看到许多的.h和.c文件。
- require的作用:
加载给定的模块
- require函数的原形为:
require(modname)
- require函数对应ll_require,ll_require的实现在loadlib.c中:
static const luaL_Reg ll_funcs[] = {
{"module", ll_module},
{"require",ll_require},
{NULL, NULL}
};
static int ll_require (lua_State *L) {
const char*name = luaL_checkstring(L, 1);
int i;
lua_settop(L,1); /* _LOADED table will be at index 2 */
lua_getfield(L,LUA_REGISTRYINDEX, "_LOADED");
lua_getfield(L,2, name);
if(lua_toboolean(L, -1)) { /* is it there? */
if(lua_touserdata(L, -1) == sentinel) /* check loops */
luaL_error(L,"loop or previous error loading module " LUA_QS, name);
return1; /* package is already loaded */
}
/* elsemust load it; iterate over available loaders */
lua_getfield(L,LUA_ENVIRONINDEX, "loaders");
if (!lua_istable(L,-1))
luaL_error(L,LUA_QL("package.loaders") " must be a table");
lua_pushliteral(L,""); /* error message accumulator */
for (i=1;; i++) {
lua_rawgeti(L,-2, i); /* get a loader */
if(lua_isnil(L, -1))
luaL_error(L,"module " LUA_QS " not found:%s",
name,lua_tostring(L, -2));
lua_pushstring(L,name);
lua_call(L,1, 1); /* call it */
if(lua_isfunction(L, -1)) /* did it find module? */
break;/* module loaded successfully */
elseif (lua_isstring(L, -1)) /* loader returned error message? */
lua_concat(L,2); /* accumulate it */
else
lua_pop(L,1);
}
lua_pushlightuserdata(L,sentinel);
lua_setfield(L,2, name); /* _LOADED[name] = sentinel */
lua_pushstring(L,name); /* pass name as argument to module */
lua_call(L,1, 1); /* run loaded module */
if (!lua_isnil(L,-1)) /* non-nil return? */
lua_setfield(L,2, name); /* _LOADED[name] = returned value */
lua_getfield(L,2, name);
if(lua_touserdata(L, -1) == sentinel) { /* module did not set a value? */
lua_pushboolean(L,1); /* use true as result */
lua_pushvalue(L,-1); /* extra copy to be returned */
lua_setfield(L,2, name); /* _LOADED[name] = true */
}
return 1;
}
- require函数的执行流程:
- require(modname)函数会在开始的时候会在lua的虚拟机(栈)中查找package.loaded表的内容以确认modname是否已经被加载,如果是,require返回存储在package.loaded[modname]中的值;
- 否则,它会尝试为该模块寻找一个加载器;
- 加载器就是加载这个模块的接口,由于require支持加载.c、.dll、.lua等多种文件,因此其源码中也提供了多个接口函数来分别实现加载这些文件,这些接口函数就是加载器;
- require从package.loaders这个表中来查找加载器,package.loaders表存放的都是函数指针,这些函数指针就是加载器。
- package.loaders的声明和实现在loadlib.c中:
static const lua_CFunction loaders[] =
{loader_preload, loader_Lua, loader_C, loader_Croot,NULL};
- require首先从package.loaders表中取出loader_preload;
- loader_preload函数从package.preload表中查找package.preload[modname],package.preload[modname]存放的也是一个函数指针,如果它不为空,这个函数指针就是一个加载器,使用这个加载器加载文件;
- 否则,require将会从package.loaders表中取出loader_Lua,loader_Lua方法则会从package.path表中存储的路径中查找.lua模块,如果查找成功,则加载这个模块;
- 如果在package.path中查找失败了,require将会从package.loaders表中取出loader_C,loader_C方法会从package.cpath表中存储的路径查找模块;
- 如果还是查找失败,require会尝试一个一体化加载器,即loader_Croot函数,loader_Croot是一个一体化加载器,也会从package.cpath中查找模块,而且能加载a.b.c这种文件,如果使用loader_C来加载a.b.c文件,它会为a查找一个C库,而loader_Croot会为a、b和c分别查找一个C库,查找完毕后,将这几个C子模块打包进一个库中。
对require函数有了初步的了解后,我们来尝试使用它将多个.lua文件加载进程序块中,让它们能相互调用。
方法1,使用package.path:
还是新建一个lua1.cpp,这回加载放在F盘LuaFile文件夹里的mainLua.lua
lua1.cpp:
// lua1.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
#include "stdio.h"
#include "string.h"
//lua.h和lualib.h作为C代码运行,在C++文件中使用lua.h和lualib.h,需使用extern命令实现混合编程,否则会提示无法解析外部函数
extern "C"
{
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}
using namespace std;
#pragma comment(lib,"lua5.1.lib")
int _tmain(int argc, _TCHAR* argv[])
{
charbuff[256];
int error;
//创建一个lua状态机(虚拟栈),这个状态机没有包含预定义的lua库函数
lua_State*L = luaL_newstate();
//打开标准库,这样状态机可以使用lua库函数,可以解释lua脚本
luaL_openlibs(L);
charfileName[] = "F:/LuaFile/mainLua.lua";
//加载lua文件,解释器将lua文件的内容读取出来,动态编译为代码块
luaL_loadfile(L,fileName);
//执行编译好的代码块,参数1为栈指针,参数2为传给待调用函数的参数数量,
//参数3为期望返回的结果的数量,参数4为错误处理函数的索引
int result= lua_pcall(L,0,LUA_MULTRET,0);
//如果运行没有错误,lua_pcall将返回0
if(!result)
{
printf_s("lua脚本运行成功\n");
}
lua_close(L);
return 0;
}
LuaFile文件夹目录如下
运行cpp程序,结果如下
能发现程序将package.path和"mainLua"打印出来了,但是并没有打印"mainLua2"和"mainLua3",说明mainLua2.lua和mainLua3.lua文件没有被成功加载。
package.path包含着当前vs程序所在的目录、lua的安装目录,lua的require方法会从这个package.path中查找目标文件,并将"?"号替代成文件名。
比如mainLua.lua中require "mainLua2",那么require方法将会从
.\mainLua2.lua
F:\VSProject\lua1\Debug\lua\mainLua2.lua
F:\VSProject\lua1\Debug\lua\mainLua2\init.lua
F:\VSProject\lua1\Debug\mainLua2.lua
F:\VSProject\lua1\Debug\mainLua2\init.lua
D:\5.1\lua\mainLua2.luac
mainLua2.lua
这几个目录中查找mainLua2.lua文件,如果没有找到,将捕获异常。mainLua2.lua此时的目录是F:\LuaFile\mainLua2.lua,因此程序没有找到文件。
我们来把mainLua.lua的内容改一下。
注意"\"是转义字符,如果在代码中用"\"表示路径,用两个"\"表示,或者用"/"代替。修改后,运行程序,就能发现程序已经找到了mainLua2.lua与mainLua3.lua文件。
使用这种方法来查找lua文件比较简便,但是不易于扩展,如果程序规模逐渐加大,需要引入其他目录的lua文件,需要频繁地修改package.path或需要在require后加上较为冗余的路径字段,比如现在多了F:\LuaFile2和F:\LuaFile\LuaFile3这两个目录,需求在程序中引入这两个目录中的所有lua文件,那mainLua.lua文件可能得这样改:
或
这样看不论哪一种都很繁琐而且不好维护,我们来看第二种关联lua的方法。
方法2,使用自定义的loader:
之前提到package.loaders中的loader_Lua方法是加载.lua模块的加载器,这个加载器会从package.path中查找.lua文件,我们可以把这个loader_Lua加载器给替换掉,使用自己的加载器来查找给定目录下的.lua文件。
首先我们准备几个.lua文件
然后我们将这四个.lua文件放置在几个不同的目录中,这里的示例目录是:
F:\LuaFile\mainLua.lua
F:\LuaFile\mainLua2.lua
F:\LuaFile\LuaFile3\mainLua3.lua
F:\LuaFile2\mainLua4.lua
然后写一个.cpp文件:
#include "stdafx.h"
#include "iostream"
#include "stdio.h"
#include "string.h"
#include "windows.h"
#include "vector"
//lua.h和lualib.h作为C代码运行,在C++文件中使用lua.h和lualib.h,需使用extern命令,否则会提示无法解析外部函数
extern "C"
{
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}
using namespace std;
#pragma comment(lib,"lua5.1.lib")
vector<string> luaPaths;
string luaName; //当前require的lua文件名
string luaFile; //当前require的lua的完整路径
bool isFind; //是否查找成功
char* WCHAR2Char(WCHAR* wc)
{
char* c;
int len=WideCharToMultiByte(CP_ACP,0,wc,wcslen(wc),NULL,0,NULL,NULL);
c=new char[len+1];
WideCharToMultiByte(CP_ACP,0,wc,wcslen(wc),c,len,NULL,NULL);
c[len]='\0';
return c;
}
//遍历目标目录及其子目录下的所有文件
void searchFile(const char path[], int level = 0)
{
if(isFind)
return;
charfind_path[128];
sprintf_s(find_path,"%s*", path);
WIN32_FIND_DATAFindFileData;
HANDLEhFind;
BOOLbContinue = TRUE;
//char*转LPCWSTR(constWCHAR*)
WCHARwszClassName[256];
memset(wszClassName,0,sizeof(wszClassName));
MultiByteToWideChar(CP_ACP,0,find_path,strlen(find_path)+1,wszClassName,
sizeof(wszClassName)/sizeof(wszClassName[0]));
hFind =FindFirstFile(wszClassName, &FindFileData);
if (hFind== INVALID_HANDLE_VALUE)
return;
while(bContinue)
{
if(_stricmp(WCHAR2Char(FindFileData.cFileName), "..") &&_stricmp(WCHAR2Char(FindFileData.cFileName), "."))
{
strings(WCHAR2Char(FindFileData.cFileName));
//如果当前文件就是require的文件,停止搜索
if(0== strcmp(s.c_str(),luaName.c_str()))
{
luaFile= path;
luaFile.append(luaName);
isFind= true;
return;
}
if(FindFileData.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)
{
sprintf_s(find_path,"%s%s/", path, WCHAR2Char(FindFileData.cFileName));
searchFile(find_path,level + 1);
}
}
bContinue= FindNextFile(hFind, &FindFileData);
}
}
int myLoader(lua_State * L)
{
isFind = false;
luaFile = "";
//为文件追加.lua后缀
stringfileName(luaL_checkstring(L, 1));
fileName.append(".lua");
//记录当前要查找的.lua文件的文件名
luaName =fileName;
for(inti=0;i<luaPaths.size();i++)
{
if(isFind)
break;
stringluaFilePath = luaPaths[i];
//luaFilePath.append(fileName);
searchFile(luaFilePath.c_str());
}
if(0 !=luaL_loadfile(L,luaFile.c_str()))
{
printf_s("loadlua file error.");
}
//返回的结果数
return 1;
}
//把package.loaders这个table第二个元素替换成myLoader
void addLuaLoader(lua_State * m_state, lua_CFunction func)
{
if (!func)return;
// stackcontent after the invoking of the function
// getloader table(获得lua预加载好的package这个table,并将这个table压桟)
lua_getglobal(m_state,"package"); /* L: package */
// 获得package这个table里面的loaders元素,并将其压桟,这个loaders也是一个table,table里的元素全是方法
lua_getfield(m_state,-1, "loaders"); /* L: package, loaders */
// insertloader into index 2(将自己的loader方法压桟)
lua_pushcfunction(m_state,func); /* L: package, loaders, func */
// 遍历loaders这个table,将其下标为2的元素替换为自己的loader方法
for (int i= lua_objlen(m_state, -2) + 1; i > 2; --i)
{
lua_rawgeti(m_state,-2, i - 1); /* L: package, loaders, func, function */
//we call lua_rawgeti, so the loader table now is at -3
lua_rawseti(m_state,-3, i); /* L: package, loaders, func */
}
lua_rawseti(m_state,-2, 2); /* L: package, loaders */
// setloaders into package
lua_setfield(m_state,-2, "loaders"); /* L: package */
lua_pop(m_state,1);
}
int _tmain(int argc, _TCHAR* argv[])
{
charbuff[256];
int error;
//创建一个lua状态机(虚拟栈),这个状态机没有包含预定义的lua库函数
lua_State*L = luaL_newstate();
//打开标准库,这样状态机可以使用lua库函数,可以解释lua脚本
luaL_openlibs(L);
luaPaths.clear();
//自定义的loader将从这两个目录去查找.lua文件
luaPaths.push_back("F:/LuaFile/");
luaPaths.push_back("F:/LuaFile2/");
//==================使用自定义的loader加载lua文件======================
addLuaLoader(L,myLoader);
//=====================================================================
charfileName[] = "F:/LuaFile/mainLua.lua";
加载lua文件,解释器将lua文件的内容读取出来,动态编译为代码块
luaL_loadfile(L,fileName);
//执行编译好的代码块,参数1为栈指针,参数2为传给待调用函数的参数数量,
//参数3为期望返回的结果的数量,参数4为错误处理函数的索引(这个索引为压入栈的函数索引,0表示没有错误处理函数)
int result= lua_pcall(L,0,LUA_MULTRET,0);
//如果运行没有错误,lua_pcall将返回0
if(!result)
{
printf_s("lua脚本运行成功\n");
}
lua_close(L);
return 0;
}
程序执行结果:
可以看到,程序已经成功把这四个.lua文件加载到了程序块中,它们之间可以相互调用彼此提供的接口。
上面的cpp程序的执行流程为:
- 在c程序中创建lua虚拟机(栈)
- 将可能要存储.lua文件的根目录保存在一个数组中,也就是luaPaths,luaPaths初始化完毕后,后面程序将会从"F:/LuaFile/"与"F:/LuaFile2/"这两个目录中查找.lua文件。这一步可以修改为读取配置文件,从配置文件中获取文件目录。
- 使用自定义loader。addLuaLoader(lua_State* m_state,lua_CFunction func)这个函数的作用是将栈中package.loaders这个表里的第二个元素(即上文提到的loader_Lua函数指针)替换成func。
- 使用luaL_loadfile方法从mainLua.lua文件中加载代码块,并将代码块压入栈中。
- main函数中执行lua_pcall方法,执行当前栈中的代码块。
- 代码块第一句被执行的语句是print("mainLua");接下来执行require"mainLua2",由于原loader方法已经被替换成myLoader,因此执行myLoader方法。
- 在myLoader方法中,使用luaL_checkstring方法获取当前虚拟机中执行的方法的参数,也就是require的参数,即“mainLua2”这个字符串,然后为这个字符串追加.lua后缀。接下来开始使用searchFile方法来循环遍历luaPaths数组中的目录,查找luaPaths[i]中以及其子目录的mainLua2.lua文件,如果查找成功,使用luaL_loadfile方法加载这个文件。加载完毕后,立即执行加载完毕的代码块,也就是print("mainLua2")。
- 继续执行require"mainLua3" 和require "mainLua4",步骤同上。
- 结束。
参考资料:http://hmanager.gw.com.cn/ID_DTS_315300.html
http://bbs.csdn.net/topics/330134613
http://blog.csdn.net/zhouxuguang236/article/details/8761497