Lua源码下载
本篇主要介绍Lua与C的互调方式。
首先下载Lua源码,这里使用Lua5.15。
lua.c和luac.c都有main方法。lua.c的main是编译运行时用的,luac.c的main是编译一个将lua代码编译成二进制代码的软件用的,使用exe打包时需要移除其中一个,这里移除luac.c。
项目地址
Lua提供了下方三个伪索引,所谓伪索引就是数据不在栈上,却可以像操作栈内元素一样使用。这里我们主要查看LUA_REGISTRYINDEX为注册表索引,可通过该索引访问到注册表。该表内只有两个子表:_LOADED和_LOADLIB。_LOADED表用于存储模块,默认有string、table、coroutine、package、_G等Lua基础模块,_LOADLIB表内只有一个__gc函数。
LUA_GLOBALSINDEX为全局表索引,即可通过该索引访问到上面的_G模块。
#define LUA_REGISTRYINDEX (-10000)
#define LUA_ENVIRONINDEX (-10001)
#define LUA_GLOBALSINDEX (-10002)
//iapi.c
static TValue *index2adr (lua_State *L, int idx) {
if (idx > 0) {
TValue *o = L->base + (idx - 1);
api_check(L, idx <= L->ci->top - L->base);
if (o >= L->top) return cast(TValue *, luaO_nilobject);
else return o;
}
else if (idx > LUA_REGISTRYINDEX) {
api_check(L, idx != 0 && -idx <= L->top - L->base);
return L->top + idx;
}
else switch (idx) { /* pseudo-indices */
case LUA_REGISTRYINDEX: return registry(L); //获取注册表
case LUA_ENVIRONINDEX: { //获取当前函数环境
Closure *func = curr_func(L);
sethvalue(L, &L->env, func->c.env);
return &L->env;
}
case LUA_GLOBALSINDEX: return gt(L); //获取全局表
default: {
Closure *func = curr_func(L);
idx = LUA_GLOBALSINDEX - idx;
return (idx <= func->c.nupvalues)
? &func->c.upvalue[idx-1]
: cast(TValue *, luaO_nilobject);
}
}
}
Lua通过require加载模块,首先会在package.loaded表中查找(loaded表也就是使用LUA_REGISTRYINDEX访问到的表),不存在再加载。
当我们需要为Lua制作插件时,可以使用CPP项目生成dll文件供lua调用。
下方为加载模块模板,函数名格式为luaopen_DLL文件名_自定义模块名。
__declspec(dllexport) int luaopen_LuaAPI_core(lua_State* L);
#include
extern "C"
{
#include "lua.h"
#include "LuaAPI.h"
#include "lauxlib.h"
}
static int Hello(lua_State* L)
{
std::cout << "hello" << std::endl;
return 0;
}
static luaL_reg Functions[] = //注册函数表,函数名对应函数地址
{
{"hello",Hello},
};
int luaopen_LuaAPI_core(lua_State * L)
{
luaL_openlib(L, "luo",Functions,0); //将luo模块加入_LOADED表内,可以改为加入_G表内
std::cout << "LuaAPI.core注册完成" << std::endl; //require "LuaAPI.core"
return 1; //表示返回一个参数
}
lua调用
local core=require "LuaAPI.core"
core.hello(); --或者luo.hello
上方Lua代码只是调用了一个C函数,并没有形参和返回值接口。Lua与宿主语言的交互通过一个虚拟栈调用,也就是说所有信息都包含在LuaState这个结构体内。
Lua可以将参数压入栈内传到C函数内,C函数从栈中获取参数并处理后可以将返回值压入栈内,传到Lua中。这里实现Lua调用一个C函数完成加法操作
static int add(lua_State* L)
{
if (lua_type(L, -2) == LUA_TNUMBER && lua_type(L, -1) == LUA_TNUMBER) //参数1和2都为number类型
{
//参数从左到右入栈,则从右到左出栈
int a=lua_tointeger(L, -2); //获取参数1
int b = lua_tointeger(L, -1); //获取参数2
lua_pop(L,2); //弹出两个参数
int sum = a + b;
lua_pushinteger(L, sum); //将返回值压入栈
}
else
{
lua_error(L);
}
return 1; //表示返回一个参数
}
//见上,注册函数中加入add
static luaL_reg Functions[] =
{
{"hello",Hello},
{"add",add},
};
lua调用
local core=require "LuaAPI.core"
core.hello(); --或者luo.hello
local result=core.add(1,2);
print(result)
附录
一个简单的打印栈函数,不打印嵌套表
static std::string typeNames[]=
{
"nil",
"boolean",
"light userdata" ,
"number",
"string",
"table",
"function",
"userdata",
"thread",
};
static void print_table(lua_State* L, int index, int nIndex)
{
printf("%d\t table\t", index);
lua_pushvalue(L, index); //将需打印的table复制至栈顶
lua_pushnil(L); //压入初始key
while (lua_next(L, -2)) //遍历表
{
switch (lua_type(L,-2))//key值
{
case LUA_TBOOLEAN:
std::cout << "key:" << (lua_toboolean(L, -2) == 1) << "\t";
break;
case LUA_TNUMBER:
std::cout << "key:" << lua_tonumber(L, -2) << "\t";
break;
case LUA_TSTRING:
std::cout << "key:" << lua_tostring(L, -2) << "\t";
break;
default:
std::cout << "key type:" << typeNames[lua_type(L, -2)] << "\t";
break;
}
std::cout << ",";
switch (lua_type(L, -1))//value值
{
case LUA_TBOOLEAN:
std::cout << "value:" << (lua_toboolean(L, -1) == 1) << "\t";
break;
case LUA_TNUMBER:
std::cout << "value:" << lua_tonumber(L, -1) << "\t";
break;
case LUA_TSTRING:
std::cout << "value:" << lua_tostring(L, -1) << "\t";
break;
default: //表嵌套未实现
std::cout << "value type:" << typeNames[lua_type(L, -1)] << "\t";
break;
}
lua_pop(L, 1); //弹出一个value,才能获取下一个value
}
lua_pop(L, 1); //弹出之前复制的表
printf("%10d\n", nIndex);
}
static int print_stack(lua_State* L)
{
int stackCount = lua_gettop(L);
if (stackCount==0)
{
return 0;
}
std::cout << "====================栈顶===================" << std::endl;
for (int i = stackCount; i > 0; i--) //存在table类型的话需要从右往左取参,所以一般从栈底开始
{
int nIndex = i - stackCount - 1;
switch (lua_type(L, i))
{
case LUA_TNIL:
printf("%d\t nil \t%d\n", i, nIndex);
break;
case LUA_TBOOLEAN:
printf("%d\t boolean:%d \t%d\n", i, lua_toboolean(L, i) == 1, nIndex);
break;
case LUA_TLIGHTUSERDATA:
printf("%d\t light userdata \t%d\n", i, nIndex);
break;
case LUA_TNUMBER:
printf("%d\t number:%lf \t%d\n", i, lua_tonumber(L, nIndex), nIndex);
break;
case LUA_TSTRING:
printf("%d\t string:%s \t%d\n", i, lua_tostring(L, i), nIndex);
break;
case LUA_TTABLE:
print_table(L, i, nIndex);
break;
case LUA_TFUNCTION:
printf("%d\t function \t%d\n", i, nIndex);
break;
case LUA_TUSERDATA:
printf("%d\t userdata \t%d\n", i, nIndex);
break;
case LUA_TTHREAD:
printf("%d\t thread \t%d\n", i, nIndex);
break;
}
}
std::cout << "====================栈尾===================" << std::endl;
return 0;
}
Lua文件的加载路径为package.path,可自行修改,默认使用exe文件路径为相对路径的基准。
Lua默认不支持中文变量,可以修改llex.c的llex方法使其支持,有三处需要修改,在下方标出。
llex.c的422行到431行
else if (isalpha(ls->current) || ls->current == '_' || ls->current > 0X80) //第一处ls->current >0x80
{
/* identifier or reserved word */
TString *ts;
do {
if (ls->current > 0X80) //第二处
{
save_and_next(ls);
}
save_and_next(ls);
} while (isalnum(ls->current) || ls->current == '_' || ls->current > 0X80); //第三处
tolua之C#与Lua的交互方式