当我练习这一部分的时候,发现了一个问题——直接调用luaopen_io会使C程序crash。我使用VC++ 2005编译C代码,使用由lua-5.1.4生成的DLL。
我查了Lua5.1参考手册,上面有几处涉及到了这个问题:
To have access to these libraries, the C host program should call the luaL_openlibs function, which opens all standard libraries. Alternatively, it can open them individually by calling luaopen_base(for the basic library), ... and
luaopen_debug
(for the debug library). These functions are declared in
lualib.h
and should not be called directly: you must call them like any other Lua C function, e.g., by using lua_call.
...
The
luaopen_*
functions (to open libraries) cannot be called directly, like a regular C function. They must be called through Lua, like a Lua function.
大体意思就是说,你不能直接调用luaopen_*这些用来打开标准库的函数,你必须通过Lua来调用它们,比如使用lua_call。
没有具体例子。那就靠自己琢磨了。
之后我尝试用lua_pcall来修改对luaopen_io的直接调用,方法如下:
lua_getglobal(L, "luaopen_io");
lua_pcall(L, 0, 0, 0);
但是结果令我很失望,lua_pcall返回2,即LUA_ERRRUN:运行时错误。
然后我又尝试使用lua_cpcall,方法如下:
lua_cpcall(L, luaopen_io, NULL);
让人欣喜的是,它可以顺利运行。
我不满足于现状,又翻出源码来看。在lua-5.1.4的源码中,linit.c这个文件里实现了luaL_openlibs这个函数,具体如下:
static const luaL_Reg lualibs[] = {
{"", luaopen_base},
{LUA_LOADLIBNAME, luaopen_package},
{LUA_TABLIBNAME, luaopen_table},
{LUA_IOLIBNAME, luaopen_io},
{LUA_OSLIBNAME, luaopen_os},
{LUA_STRLIBNAME, luaopen_string},
{LUA_MATHLIBNAME, luaopen_math},
{LUA_DBLIBNAME, luaopen_debug},
{NULL, NULL}
};
LUALIB_API void luaL_openlibs (lua_State *L) {
const luaL_Reg *lib = lualibs;
for (; lib->func; lib++) {
lua_pushcfunction(L, lib->func);
lua_pushstring(L, lib->name);
lua_call(L, 1, 0);
}
}
有源码,自然一切一目了然。使用lua_call来间接调用luaopen_*的方法就是彩色字体显示的,先将函数压栈,再将参数(库的名字)压栈,最后调用lua_call(一个参数,0个返回值)。
我又上网查找相关主题。时间充裕的情况下,我对解决方案的态度是务求尽善尽美。
网上对相关主题的解释很少,都是提问题,没找到完美的解答。
我在这里总结一下吧,希望对读这篇文章的人有所裨益。
首先,官方文档里面提到不要直接调用luaopen_*这些函数,那么就不要直接调用了。
不能直接调用的原因,我也不清楚。表面看来就是liolib.c中的luaopen_io函数的实现里调用了三个newfenv和一个lua_setfenv,它们创建并设置了新的环境。但是为什么新的环境就使程序crash了呢?以我现在的能力还无法给出解释。
使用lua_call、lua_pcall、lua_cpcall来间接调用这些函数。
具体方法:
1. lua_cpcall(L, luaopen_io, NULL);
2. lua_pushcfunction(L, luaopen_io);
lua_pushstring(L, LUA_IOLIBNAME);
lua_pcall(L, 1, 0, 0);
3. lua_pushcfunction(L, luaopen_io);
lua_pushstring(L, LUA_IOLIBNAME);
lua_call(L, 1, 0);
需要补充说明的是, lua_cpcall、lua_pcall都可以检查返回值来判断是否出错;lua_call没有返回值,如果调用这个函数出问题,典型结果就是程序crash。所以我比较倾向于使用lua_cpcall或lua_pcall,因为它们让你来决定如何处理打开标准库失败的情况。lua_pcall、lua_call既可以调用Lua函数也可以调用lua_CFunction类型的C函数,而lua_cpcall只能用来调用lua_CFunction类型的C函数。当然,想了解细节还是要阅读参考手册和源码。
给大家一个整体感觉吧:
...
int main(void)
{
...
lua_State *L = lua_open();
if ( NULL == L ) {
fprintf(stderr, "lua_open() failed\n");
return -1;
}
lua_pushcfunction(L, luaopen_base); /* 间接调用luaopen_base */
lua_pushstring(L, "");
lua_call(L, 1, 0);
lua_pushcfunction(L, luaopen_math); /* 间接调用luaopen_math */
lua_pushstring(L, LUA_MATHLIBNAME);
if (lua_pcall(L, 1, 0, 0) != 0)
goto EXITAPP;
if (lua_cpcall(L, luaopen_io, NULL) != 0) /* 间接调用luaopen_io */
goto EXITAPP;
if (lua_cpcall(L, luaopen_os, NULL) != 0)
goto EXITAPP;
if (lua_cpcall(L, luaopen_package, NULL) != 0)
goto EXITAPP;
if (lua_cpcall(L, luaopen_string, NULL) != 0)
goto EXITAPP;
if (lua_cpcall(L, luaopen_table, NULL) != 0)
goto EXITAPP;
...
lua_close(L);
return 0;
EXITAPP:
lua_close(L);
return -1;
}
以上的想法、做法都是特定于我自己的开发环境。如果你的开发环境和我的不一样,我提供的方法仅供参考。你要尝试不同方法来得到解决问题的办法,多加努力。
阅读(1475) | 评论(0) | 转发(0) |