Lua与C/C++互操作
Lua通过一个“虚拟栈”(Lua State)与C/C++程序进行数据交互。
当在Lua里面操作这个栈的时候,每次操作的都是栈的顶部。而Lua的C API则有更多的控制权,可非常灵活地操纵这个栈的任意位置。
c/c++调用lua实际上是:c/c++获取全局表中的lua变量或函数,然后把数据放入栈中,lua再去栈中取数据,然后返回数据对应的值到栈顶,再由栈顶返回c++。
lua调c/c++也一样:先将c/c++的函数注册到lua解释器中,然后lua再去调用它们。
栈(Lua State)
注1:绝对索引是从1开始由栈底到栈顶依次增长的
注2:相对索引是从-1开始由栈顶到栈底依次递减的(在lua API函数内部会将相对索引转换为绝对索引)
注3:上图栈的容量为7,栈顶绝对索引为5,有效索引范围为:[1, 5],可接受索引范围为:[1, 7]
注4:Lua虚拟机指令里寄存器索引是从0开始的,而Lua API里的栈索引是从1开始的,因此当需要把寄存器索引当成栈索引使用时,要进行+1
栈是FILO(先进后出)的。栈中每个元素为一个TValue类型。64位系统下,sizeof(TValue)=16,sizeof(Value)=8
其中boolean(布尔)、integer(整型)、double(浮点)、light userdata、light c function是直接存在栈上的
TString、Udata、Closure、Table、lua state在栈上只是一个指针,都为GC类型,当没有被引用时会被lua的GC系统自动回收,具体结构如下:
将不同类型的变量压栈
static int Square(lua_State* L) { double d = lua_tonumber(L, 1); /* get argument */ lua_pushnumber(L, d*d); /* push result */ return 1; /* number of results */ } #include <string.h> static int CClosureStrLen(lua_State* L) { const char* upval = lua_tostring(L, lua_upvalueindex(1));// get first upvalue lua_pushnumber(L, (int)strlen(upval)); /* push result */ return 1; } typedef struct Rect { float w, h; } Rect; Rect g_rc; /************** 测试代码 **************/ lua_settop(L, 0); //把栈上所有元素移除 lua_pushnil(L); // 把nil压栈 lua_pushboolean(L, 1); // 把布尔值true压栈 lua_pushinteger(L, 35);// 把整型数35压栈 lua_pushnumber(L, 12.8);// 把浮点数12.8压栈 lua_pushcfunction(L, Square);// 把c函数Square压栈 lua_pushlightuserdata(L, &g_rc);// 把全局变量Rect g_rc的指针压栈 lua_pushstring(L, "Hello!");// 把字符串Hello!压栈 lua_newtable(L);// 创建一个空表并压栈 lua_newthread(L);// 创建一个Thread并压栈 lua_newuserdata(L, sizeof(Rect)); //创建一个内存块为Rect的full userdata,并压栈 lua_pushstring(L, "cclosure upvalue");// 创建一个字符串upvalue,内容为cclosure upvalue,并压栈 lua_pushcclosure(L, CClosureStrLen, 1);// 创建有1个upvalue的c函数闭包(upvalue为栈顶元素),成功后将栈顶1个upvalue出栈,并将自己入栈
以压入一个int整型数来说明压栈的实现:
void PushInt(lua_State* L, int n) { TValue v; v.tt_ = LUA_TNUMINT;//19,表示整型 v.value_.i = n; TValue* io = (L->top - 1); *io = v; // 将TValue v压入LuaState栈中 L->top++; // LuaState的top指针上移 }
打印栈
#include "lobject.h" #include "lstate.h" const TValue luaO_nilobject_ = { NILCONSTANT }; /* value at a non-valid index */ #define NONVALIDVALUE cast(TValue *, luaO_nilobject) /* test for pseudo index */ #define ispseudo(i) ((i) <= LUA_REGISTRYINDEX) static TValue* index2addr(lua_State* L, int idx) { CallInfo* ci = L->ci; if (idx > 0) { TValue* o = ci->func + idx; api_check(L, idx <= ci->top - (ci->func + 1), "unacceptable index"); if (o >= L->top) return NONVALIDVALUE; else return o; } else if (!ispseudo(idx)) { /* negative index */ api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index"); return L->top + idx; } else if (idx == LUA_REGISTRYINDEX) return &G(L)->l_registry; else { /* upvalues */ idx = LUA_REGISTRYINDEX - idx; api_check(L, idx <= MAXUPVAL + 1, "upvalue index too large"); if (ttislcf(ci->func)) /* light C function? */ return NONVALIDVALUE; /* it has no upvalues */ else { CClosure* func = clCvalue(ci->func); return (idx <= func->nupvalues) ? &func->upvalue[idx - 1] : NONVALIDVALUE; } } } void DumpLuaStack(lua_State* L) { int ad = -1; int i = lua_gettop(L); printf("\n---------------- Stack Dump ----------------\n"); while (i) { StkId o = index2addr(L, i); int t = lua_type(L, i); switch (t) { case LUA_TSTRING: printf("%d[%d]:'%s'\n", i, ad, lua_tostring(L, i)); break; case LUA_TBOOLEAN: printf("%d[%d]: %s\n", i, ad, lua_toboolean(L, i) ? "true" : "false"); break; case LUA_TNUMBER: printf("%d[%d]: %g\n", i, ad, lua_tonumber(L, i)); break; case LUA_TFUNCTION: if (ttislcf(o)) { printf("%d[%d]: c %p\n", i, ad, fvalue(o)); // lua_CFunction } else if (ttisCclosure(o)) { printf("%d[%d]: c closure %p\n", i, ad, clCvalue(o)->f); // CClosure } else if (ttisLclosure(o)) { Proto* pinfo = clLvalue(o)->p; printf("%d[%d]: lua closure %s[%d,%d]\n", i, ad, getstr(pinfo->source), pinfo->linedefined, pinfo->lastlinedefined); } break; case LUA_TTABLE: printf("%d[%d]: table:%p\n", i, ad, hvalue(o)); // 等价于printf("%d[%d]: table:%p\n", i, ad, lua_topointer(L, i)); break; case LUA_TLIGHTUSERDATA: printf("%d[%d]: light userdata:%p\n", i, ad, pvalue(o)); // 等价于printf("%d[%d]: light userdata:%p\n", i, ad, lua_topointer(L, i)); break; case LUA_TUSERDATA: printf("%d[%d]: full userdata:%p\n", i, ad, uvalue(o)); break; case LUA_TTHREAD: printf("%d[%d]: thread:%p\n", i, ad, thvalue(o)); // 等价于printf("%d[%d]: thread:%p\n", i, ad, lua_topointer(L, i)); break; default: printf("%d[%d]: %s\n", i, ad, lua_typename(L, t)); break; } i--; ad--; } printf("---------------------------------------------\n"); }
上面示例的不同类型变量压栈的最终结果如下:
C++解释执行lua文件
Test1.lua内容如下:
print("Hello, Lua!")
LuaTest.cpp代码如下:
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } int main(int argc, char *argv[]) { lua_State* L = lua_open(); // 创建一个lua虚拟机 luaL_openlibs(L); // 打开状态机L中的所有 Lua 标准库 luaL_dofile(L, "Test1.lua"); // 载入Test.lua并解释执行 lua_close(L); // 关闭lua虚拟机 return 0; }
注:luaL_dofile函数实际上是执行了luaL_loadfile来加载lua文件,加载成功之后会编译该代码块为一个Lua闭包放置在栈顶,并返回0。由于返回值为假,会继续调用lua_pcall来执行该Lua闭包,最后把该Lua闭包弹出栈。
#define luaL_dofile(L, fn) \ (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))
C++访问lua
C++调用lua函数
Test2.lua内容如下:
function add(x,y) return x + y end mytable={} function mytable.StaticFunc() print("mytable.StaticFunc called.") end function mytable:Func() print("mytable:Func self:", self) end
LuaTest.cpp代码如下:
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } int main(int argc, char *argv[]) { lua_State* L = lua_open(); luaL_openlibs(L); luaL_dofile(L, "Test2.lua"); lua_getglobal(L, "add"); // 获取全局函数add,并压入栈顶 lua_pushinteger(L, 30); // 将整型的值30压入栈顶 lua_pushinteger(L, 50); // 将整型的值50压入栈顶 lua_call(L, 2, 1); // 对栈顶的30和50执行add函数调用,执行完后将30, 50,add弹出栈,将结果80压栈 注:2为参数个数,1为返回值个数 int sum = (int)lua_tointeger(L, -1); // 将栈顶80赋值给sum变量 lua_pop(L, 1); // 从栈顶弹出1个元素 printf("The sum is: %d\n", sum); // 调用mytable表的静态函数 lua_getglobal(L, "mytable"); // 将名为mytable的全局table变量的值压栈 lua_pushstring(L, "StaticFunc"); // 将函数名为StaticFunc压栈 lua_gettable(L, -2); // 从索引为-2处的表中,读取key(在栈顶处)为StaticFunc的函数名 读取成功后,将key出栈,并将读取到的函数名入栈 lua_call(L, 0, 0); // 执行完后将StaticFunc弹出栈 注: 第一个0表示参数个数为0,第二个0表示无返回值 // 调用mytable表的成员函数 采用新方法获取函数名 lua_getfield(L, -1, "Func");// 从索引为-1处的表中,读取key为Func的函数名 成功后将读取到的函数名入栈 lua_pushvalue(L, -2); // 将索引为-2处的表复制一份并压入栈顶 lua_call(L, 1, 0); // 执行完后将Func弹出栈 注: 1表示参数个数,即self指针,为当前table,第二个0表示无返回值 lua_close(L); return 0; }
C++读写Lua中的全局变量
Test3.lua内容如下:
sayhi="Hello Lua!" mytable={sex = "male", age=18}
LuaTest.cpp代码如下:
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } int main(int argc, char *argv[]) { lua_State* L = lua_open(); luaL_openlibs(L); luaL_dofile(L, "Test3.lua"); lua_settop(L, 0); //把栈上所有元素移除 // ******读取名为sayhi的字符串全局变量的值****** lua_getglobal(L, "sayhi");// 将名为sayhi的全局string变量的值压栈 if (lua_isstring(L, -1) != 0) { const char* str = lua_tostring(L, -1); // 从栈顶读取字符串内容 printf("str: %s\n", str); } // ******修改名为sayhi的字符串全局变量的值****** lua_pop(L, 1); // 从栈顶弹出1个元素 lua_pushstring(L, "Welcome Lua!"); // 将Welcome Lua!字符串压栈 lua_setglobal(L, "sayhi");// 将栈顶的元素设置给全局变量sayhi lua_getglobal(L, "sayhi"); const char* str2 = lua_tostring(L, -1); // str2为Welcome Lua! // ******读取名为mytable的table全局变量中的内容****** lua_getglobal(L, "mytable"); // 将名为mytable的全局table变量的值压栈 lua_pushstring(L, "sex"); // 将字符串sex压栈 lua_gettable(L, -2); // 从索引为-2处的表中,读取key(在栈顶处)为sex的元素 读取成功后,将key出栈,并将读取到的元素入栈 // 另外一种方式:读取table中的某个key的值 lua_getfield(L, -2, "age");// 从索引为-2处的表中,读取key为age的元素 成功后将读取到的元素入栈 int age = (int)lua_tointeger(L, -1); // 从栈顶读取age 为18 const char* sex = lua_tostring(L, -2); // 从索引为-2处读取sex 为male // ******修改名为mytable的table全局变量中的内容****** lua_pop(L, 2); // 从栈顶弹出2个元素 lua_pushstring(L, "age"); // 将字符串age压栈 lua_pushinteger(L, 20); // 将整数20压栈 lua_settable(L, -3); // 设置索引为-3处的表的key(在栈顶下一个,即age)对应的value为20(在栈顶处) 设置成功后,将栈顶的2个元素都出栈 注:也可使用lua_rawset(L, -3)做一次直接赋值(不触发元方法) // 另外一种方式:设置table中的某个key的值 lua_pushstring(L, "female"); // 将字符串female压栈 lua_setfield(L, -2, "sex"); // 设置索引为-2处的表的key为sex对应的value为female(在栈顶处) 设置成功后,将栈顶的female出栈 lua_getfield(L, -1, "sex"); // 从索引为-1处的表中,读取key为sex的元素 成功后将读取到的元素入栈 lua_getfield(L, -2, "age"); // 从索引为-2处的表中,读取key为age的元素 成功后将读取到的元素入栈 int age2 = (int)lua_tointeger(L, -1); // 从栈顶读取age2 为20 const char* sex2 = lua_tostring(L, -2); // 从索引为-2处读取sex 为female lua_close(L); return 0; }
Lua访问C++(无dll)
Lua调用C++全局函数
Test4.lua内容如下:
local avg, sum = average(10,20,30,40,50) print("The average is ", avg) print("The sum is ", sum)
LuaTest.cpp代码如下:
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } static int Average(lua_State *L) { int n = lua_gettop(L); // 获取栈上元素的个数 double sum = 0; for (int i = 1; i <= n; ++i) { sum += lua_tonumber(L, i); // 依次去除索引为1到n的元素,并累加到sum } lua_pushnumber(L, sum / n); // 将sum/n计算得到平均值压栈 lua_pushnumber(L, sum); // 将sum压栈 return 2; // 表明有2个返回值 } int main(int argc, char *argv[]) { lua_State* L = lua_open(); luaL_openlibs(L); lua_register(L, "average", Average); // 将c++的Average函数注册成lua中的average方法 luaL_dofile(L, "Test4.lua"); lua_close(L); return 0; }
注:lua_register先用lua_pushcfunction把在c++函数压入栈中,然后调用lua_setglobal来设置栈顶的c++函数对应lua函数名,最后弹出栈顶的c++函数。
这样就可以把lua函数和c++函数建立绑定关系,使得在后续的lua脚本中使用lua函数名来调用该c++函数。
#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
Lua调用C++成员函数
下面示例也是MyArray的userdata的实现
Test5.lua内容如下:
function MyArrayTest(size) local a1 = myarray.new(size) myarray.set(a1, 1, 25.6) print(myarray.size(a1)) print(myarray.get(a1, 1)) end
LuaTest.cpp代码如下:
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } class MyArray { public: int GetSize() { return size; } void SetSize(int insize) { size = insize; } double GetAt(int index) { return values[index]; } void SetAt(int index, double value) { values[index] = value; } private: int size; double values[1]; }; static int newarray(lua_State* L) { int n = (int)luaL_checkinteger(L, 1); // 检查栈上索引为1处的元素是否为整型,并返回该元素 size_t nbytes = sizeof(MyArray) + (n - 1) * sizeof(double); MyArray* a = (MyArray*)lua_newuserdata(L, nbytes); // 创建一个userdata,并压栈 a->SetSize(n);// 设置数组的大小 return 1;//表示有1个返回值 } static int setarray(lua_State* L) { MyArray* a = (MyArray*)lua_touserdata(L, 1);// 获取栈上索引为1处的元素,并转换为userdata指针 int index = (int)luaL_checkinteger(L, 2);// 检查栈上索引为1处的元素是否为整型,并返回该元素 double value = luaL_checknumber(L, 3);// 检查栈上索引为1处的元素是否为number类型,并返回该元素 luaL_argcheck(L, a != nullptr, 1, "'array' expected"); luaL_argcheck(L, 1 <= index && index <= a->GetSize(), 2, "index out of range"); a->SetAt(index-1, value); // 设置数组索引为index-1处的值为value return 0; //表示没有返回值 } static int getarray(lua_State* L) { MyArray* a = (MyArray*)lua_touserdata(L, 1);// 获取栈上索引为1处的元素,并转换为userdata指针 int index = (int)luaL_checkinteger(L, 2);// 检查栈上索引为1处的元素是否为整型,并返回该元素 luaL_argcheck(L, a != nullptr, 1, "'array' expected"); luaL_argcheck(L, 1 <= index && index <= a->GetSize(), 2, "index out of range"); double value = a->GetAt(index-1);// 获取数组索引为index-1处的值 lua_pushnumber(L, value); // 将获取的值压栈 return 1;//表示有1个返回值 } static int getsize(lua_State* L) { MyArray* a = (MyArray*)lua_touserdata(L, 1); // 获取栈上索引为1处的元素,并转换为userdata指针 luaL_argcheck(L, a != nullptr, 1, "'array' expected"); lua_pushnumber(L, a->GetSize()); // 获取数组的大小并压栈 return 1;//表示有1个返回值 } static const struct luaL_Reg MyArrayLib[] = { {"new", newarray}, {"set", setarray}, {"get", getarray}, {"size", getsize}, {nullptr, nullptr} }; int luaopen_MyArray(lua_State* L) { luaL_newlib(L, MyArrayLib); return 1; } int main(int argc, char *argv[]) { lua_State* L = lua_open(); luaL_openlibs(L); luaL_requiref(L, "myarray", luaopen_MyArray, 1); // 将MyArray相关方法注册到全局表中,lua中的名为myarray luaL_dofile(L, "Test5.lua"); lua_getglobal(L, "MyArrayTest"); // 将函数名MyArrayTest压栈 lua_pushinteger(L, 1000); // 传入MyArray的size为1000 压栈 lua_call(L, 1, 0); // 执行完后将MyArrayTest弹出栈 注: 1表示参数个数,第二个0表示无返回值 lua_close(L); return 0; }
注:luaL_newlib中一共包含3个函数
luaL_checkversion: 检查Lua版本是否一致
luaL_newlibtable: 创建一个table并压入栈顶,这其实也是一个宏,实际上调用的是lua_createtable。
luaL_setfuncs: 将luaL_Reg函数列表设置给刚刚压入栈的表。luaL_Reg函数列表是一个名字(key)和函数指针(value)组成的数组。
#define luaL_newlib(L,l) \ (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
Lua访问C++(独立dll模块)
Test6.lua内容如下:
local myarray=require "myLualib" local avg, sum = average(10,20,30,40,50) print("The average is ", avg) print("The sum is ", sum) function MyArrayTest(size) local a1 = myarray.new(size) myarray.set(a1, 1, 25.6) print(myarray.size(a1)) print(myarray.get(a1, 1)) end MyArrayTest(1000)
注1:local myarray=require "myLualib"等价于以下代码
local myarray = nil local fnluaopen_myLualib = package.loadlib("myLualib.dll","luaopen_myLualib") -- 查找myLualib.dll中名为luaopen_myLualib函数 if fnluaopen_myLualib ~= nil then return myarray=fnluaopen_myLualib() -- 执行luaopen_myLualib函数 end
注2:为了保证能找到myLublib.dll,可将dll文件复制到Test5.lua文件的目录下
myLualib.dll模块
/*************************** myLualib.h ***************************/ #pragma once extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } #ifdef MYLUALIB_EXPORTS #define MYLUALIB_API __declspec(dllexport) #else #define MYLUALIB_API __declspec(dllimport) #endif extern "C" MYLUALIB_API int luaopen_myLualib(lua_State *L);//定义导出函数 /*************************** myLualib.cpp ***************************/ #include "myLualib.h" static int Average(lua_State *L) { int n = lua_gettop(L); // 获取栈上元素的个数 double sum = 0; for (int i = 1; i <= n; ++i) { sum += lua_tonumber(L, i); // 依次去除索引为1到n的元素,并累加到sum } lua_pushnumber(L, sum / n); // 将sum/n计算得到平均值压栈 lua_pushnumber(L, sum); // 将sum压栈 return 2; // 表明有2个返回值 } class MyArray { public: int GetSize() { return size; } void SetSize(int insize) { size = insize; } double GetAt(int index) { return values[index]; } void SetAt(int index, double value) { values[index] = value; } private: int size; double values[1]; }; static int newarray(lua_State* L) { int n = (int)luaL_checkinteger(L, 1); // 检查栈上索引为1处的元素是否为整型,并返回该元素 size_t nbytes = sizeof(MyArray) + (n - 1) * sizeof(double); MyArray* a = (MyArray*)lua_newuserdata(L, nbytes); // 创建一个userdata,并压栈 a->SetSize(n);// 设置数组的大小 return 1;//表示有1个返回值 } static int setarray(lua_State* L) { MyArray* a = (MyArray*)lua_touserdata(L, 1);// 获取栈上索引为1处的元素,并转换为userdata指针 int index = (int)luaL_checkinteger(L, 2);// 检查栈上索引为1处的元素是否为整型,并返回该元素 double value = luaL_checknumber(L, 3);// 检查栈上索引为1处的元素是否为number类型,并返回该元素 luaL_argcheck(L, a != nullptr, 1, "'array' expected"); luaL_argcheck(L, 1 <= index && index <= a->GetSize(), 2, "index out of range"); a->SetAt(index-1, value); // 设置数组索引为index-1处的值为value return 0; //表示没有返回值 } static int getarray(lua_State* L) { MyArray* a = (MyArray*)lua_touserdata(L, 1);// 获取栈上索引为1处的元素,并转换为userdata指针 int index = (int)luaL_checkinteger(L, 2);// 检查栈上索引为1处的元素是否为整型,并返回该元素 luaL_argcheck(L, a != nullptr, 1, "'array' expected"); luaL_argcheck(L, 1 <= index && index <= a->GetSize(), 2, "index out of range"); double value = a->GetAt(index-1);// 获取数组索引为index-1处的值 lua_pushnumber(L, value); // 将获取的值压栈 return 1;//表示有1个返回值 } static int getsize(lua_State* L) { MyArray* a = (MyArray*)lua_touserdata(L, 1); // 获取栈上索引为1处的元素,并转换为userdata指针 luaL_argcheck(L, a != nullptr, 1, "'array' expected"); lua_pushnumber(L, a->GetSize()); // 获取数组的大小并压栈 return 1;//表示有1个返回值 } static const struct luaL_Reg MyArrayLib[] = { {"new", newarray}, {"set", setarray}, {"get", getarray}, {"size", getsize}, {nullptr, nullptr} }; int luaopen_myLualib(lua_State* L) { lua_register(L, "average", Average); // 将c++的Average函数注册成lua中的average方法 luaL_newlib(L, MyArrayLib); return 1; }
注:MyLuaLib模块不要直接集成lua虚拟机的源代码,应导入lua虚拟机的dll来使用,否则会报如下错误
multiple Lua VMs detected
exe宿主程序
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } int main(int argc, char *argv[]) { lua_State* L = lua_open(); luaL_openlibs(L); luaL_dofile(L, "Test6.lua"); lua_close(L); return 0; }
参考
Lua 5.3 Reference Manual(官方英文版)