转载自:https://blog.csdn.net/v_xchen_v/article/details/77249332
一、Lua与C++的交互机制——Lua堆栈
1)交互机制
Lua和C++ 的交互机制的基础在于Lua提供了一个虚拟栈,C++ 和Lua之间的所有类型的数据交换都通过这个栈完成。无论何时C想从Lua中调用一个值,被请求的值将会被压入栈,无论何时C想要传递一个值给Lua,首先将整个值压栈,然后就可以在Lua中调用。
栈中每个元素都能保存任何类型的Lua值(实际上Lua的任何类型的值包括字符串、表等等,最终都是以TValue这种数据结构来保存的)
当在C代码中要获取Lua的一个值的时候,只需要调用Lua API将指定值压入栈中,C再获取栈值
API有一系列的压栈函数,为了将栈顶的Lua值转换成C值,我们为每种类型定义一个对应的函数:
void lua_pushnil(Lua_State *L);
void lua_pushboolean(Lua_State *L,int bool);
void lua_pushnumber(Lua_State *L,double n);
void lua_pushstring(Lua_State*L,const char*s);
void lua_pushuserdata(Lua_State*L,void*p);
API有一系列的从栈中获取值的函数,为了将栈中的转换C值成Lua值
bool lua_toboolean(Lua_State*L,int idx);
int lua_tonumber(Lua_State*L,int idx);
const char* lua_tostring(Lua_State*L,int idx,size_t *len);
void* lua_touserdata(Lua_State*L,int idx);
Lua API还提供了一套lua_is*来检查一个元素是否是一个指定的类型
int lua_isboolean(Lua_State*L,int idx);
int lua_isnumber(Lua_State*L,int idx);
int lua_isstring(Lua_State*L,int idx);
此外还提供了API函数来人工控制堆栈:
int lua_gettop(luaState *L);
void lua_settop(luaState *L,int idx);
void lua_pushvalue(luaState *L,int idx);
void lua_remove(luaState *L,int idx);
void lua_insert(luaState *L,int idx);
void lua_replace(luaState *L,int idx);
Lua API函数参照官方手册:http://www.lua.org/manual/5.3/
Lua中,对虚拟栈提供正向索引和反向索引两种索引方式,假设当前Lua的栈中有5个元素,如下图所示:
当C和Lua互相调用的时候,Lua虚拟栈严格的按照LIFO规则操作,只会改变栈顶部分。但通过Lua的API,可以查询栈上的任何元素,甚至是在任何一个位置插入和删除元素。
2)示例
以字符串和数字值为例试一下对Lua栈的操作
环境:IDE Clion
资源:Lua源文件包中除了lua.c和luac.c(这两个源文件中都有main函数,其中lua.c编译之后就是我们常用的lua.exe)的所有源文件 源文件下载地址
配置CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(luac)
set(CMAKE_CXX_STANDARD 11)
set(SOURCE_FILES main.cpp ltablib.c lauxlib.c lbaselib.c lapi.c lbitlib.c lcode.c lcorolib.c lctype.c ldblib.c ldebug.c ldo.c ldump.c lfunc.c lgc.c linit.c liolib.c llex.c llimits.h lmathlib.c lmem.c loadlib.c lobject.c lopcodes.c loslib.c lparser.c lstate.c lstring.c lstrlib.c ltable.c ltm.c lundump.c lutf8lib.c lvm.c lzio.c)
add_executable(luac ${SOURCE_FILES})
main.cpp
#include "lua.hpp"
#include <iostream>
int main()
{
//创建一个state
lua_State *L = luaL_newstate();
//入栈
lua_pushstring(L,"i am testing lua & c++");
lua_pushnumber(L,123);
//读栈取值
if(lua_isstring(L,-2))//或if(lua_isstring(L,1))
{
std::cout<<lua_tostring(L,-2)<<std::endl;
}
if(lua_isnumber(L,-1))
{
std::cout<<lua_tonumber(L,-1)<<std::endl;
}
//关闭state
lua_close(L);
return 0;
}
补充:
++Lua虚拟栈在源码中是如何实现的++:
Lua栈是在创建lua_State时建立的,TValue stack[max_stack_len] // 欲知内情可以查 Lua源码lstate.c的stack_init函数
Lua栈可以存储数字,字符串,表,闭包等,它们最终都用TValue这种数据结构来保存
TValue结构对应于lua中的所有数据类型, 是一个{值, 类型} 结构, 这就lua中动态类型的实现, 它把值和类型绑在一起, 用tt记录value的类型, value是一个联合结构, 由Value定义, 可以看到这个联合有四个域, 先说明简单的
p – 可以存一个指针, 实际上是lua中的light userdata结构
n – 所有的数值存在这里, 不过是int , 还是float
b – Boolean值存在这里, 注意, lua_pushinteger不是存在这里, 而是存在n中, b只存布尔
gc – 其他诸如table, thread, closure, string需要内存管理垃圾回收的类型都存在这里
gc是一个指针, 它可以指向的类型由联合体GCObject定义, 从图中可以看出, 有string, userdata, closure, table, proto, upvalue, thread
可以的得出如下结论:
1. lua中, number, boolean, nil, light userdata四种类型的值是直接存在栈上元素里的, 和垃圾回收无关.
2. lua中, string, table, closure, userdata, thread存在栈上元素里的只是指针, 他们都会在生命周期结束后被垃圾回收
二、C++ 调用 Lua
C++ 可以获取Lua中的值,可以调用Lua函数,还可以修改Lua文件
1)C获取Lua值
使用lua_getglocal来获取值,然后将其压栈
使用C API lua_toXXX将栈中元素取出转成相应的C类型的值
如果Lua值为table类型的话,通过lua_getfield和lua_setfield获取和修改表中元素的值
2)C调用Lua函数
使用lua_getglobal来获取函数,然后将其压入栈;
如果这个函数有参数的话,就需要依次将函数的参数也压入栈;
这些准备工作都准备就绪以后,就调用lua_pcall开始调用函数了,调用完成以后,会将返回值压入栈中;
最后取返回值得过程不用多说了,调用完毕。
示例:
新建一个简单的lua文件名为luac.lua放到生成exe的同级目录下
luac.lua
name = "xchen"
version = 1
me = { name = "xchen", gender = "female"}
function add (a,b)
return a+b
end
main.cpp
#include "lua.hpp"
#include <iostream>
using namespace std;
//显示栈内情况
static void stackDump(lua_State* L);
int main()
{
//创建一个state
lua_State *L = luaL_newstate();
luaL_openlibs(L);
//读lua文件
int fret = luaL_dofile(L,"luac.lua");
if(fret)
{
std::cout<<"read lua file error!"<<std::endl;
}
//读取变量
lua_getglobal(L,"name"); //string to be indexed
std::cout<<"name = "<<lua_tostring(L,-1)<<std::endl;
//读取数字
lua_getglobal(L,"version"); //number to be indexed
std::cout<<"version = "<<lua_tonumber(L,-1)<<std::endl;
//读取表
lua_getglobal(L, "me"); //table to be indexed
if(!lua_istable(L,-1))
{
std::cout<<"error:it is not a table"<<std::endl;
}
//取表中元素
lua_getfield(L, -1 ,"name");
std::cout<<"student name = "<<lua_tostring(L,-1)<<std::endl;
lua_getfield(L,-2,"gender");
std::cout<<"student gender = "<<lua_tostring(L,-1)<<std::endl;
//修改表中元素
lua_pushstring(L, "007");
lua_setfield(L,-4, "name");
lua_getfield(L, -3 ,"name");
std::cout<<"student newName = "<<lua_tostring(L,-1)<<std::endl;
//取函数
lua_getglobal(L,"add");
lua_pushnumber(L,15);
lua_pushnumber(L,5);
lua_pcall(L,2,1,0);//2-参数格式,1-返回值个数,调用函数,函数执行完,会将返回值压入栈中
std::cout<<"5 + 15 = "<<lua_tonumber(L,-1)<<std::endl;
//查看栈
stackDump(L);
//关闭state
lua_close(L);
return 0;
}
static void stackDump(lua_State* L) {
cout << "\nbegin dump lua stack" << endl;
int i = 0;
int top = lua_gettop(L);
for (i = 1; i <= top; ++i) {
int t = lua_type(L, i);
switch (t) {
case LUA_TSTRING: {
printf("'%s' ", lua_tostring(L, i));
}
break;
case LUA_TBOOLEAN: {
printf(lua_toboolean(L, i) ? "true " : "false ");
}
break;
case LUA_TNUMBER: {
printf("%g ", lua_tonumber(L, i));
}
break;
default: {
printf("%s ", lua_typename(L, t));
}
break;
}
}
cout << "\nend dump lua stack" << endl;
}
https://img-blog.csdn.net/20170816160051151)
三、Lua 调用 C++
Lua可以调用由C++定义、实现具体的函数
步骤:
1. 将C的函数包装成Lua环境认可的函数
1. 将包装好的函数注册到Lua环境中
1. 像使用普通Lua函数那样使用注册函数
包装C函数
为了从Lua脚本中调用C函数,需要将被调用的C函数从普通的C函数包装成Lua_CFunction格式,并需要在函数中将返回值压入栈中,并返回返回值个数:
int (Lua_CFunction*)(lua_state*)
{
// c code // 实现逻辑功能
// lua_push code // 需要将返回值压入堆栈
return n; // n为具体的返回值个数,告诉解释器,函数向堆栈压入几个返回值
}
例如
int add(int a,int b)
{
return a+b;
}
1
2
3
4
包装为:
int add(lua_state*L)
{
int a = lua_tonumber(-1);
int b = lua_tonumber(-2);
int sum = a+b;
lua_pushnumber(L,sum);
return 1;
}
注册C函数到Lua环境
将函数压栈,然后给函数设置一个在Lua中调用的名称
// 参数n为Lua注册的函数名,f为对应的c函数名
lua_register(L,n,f);
1
2
lua_register是一个宏,对应两个函数:lua_pushfunction(L,f)和lua_setglobal(L,n),将函数存放在一个全局table中。
lua_register(L,"Add2Number",add);//将c函数add注册到全局table[Add2Number]中
1
使用注册函数
像使用普通函数一样使用注册函数
Add2Number(1,2);
1
示例
test.lua
print("Hi! " .. sayHi("xchen"))
1
main.cpp
#include "lua.hpp"
#include <iostream>
using namespace std;
//C++中定义、实现函数sayHi
int sayHi(lua_State *L)
{
//获取lua函数中的第一个参数
string name = luaL_checkstring(L,1);
//压栈
lua_pushstring(L,name.data());
return 1;
}
int main()
{
//创建一个state
lua_State *L = luaL_newstate();
luaL_openlibs(L);
//为Lua注册名为第一个参数的函数,实际上是调用c++中第三个参数名的函数
lua_register(L, "sayHi" ,sayHi);
//读lua文件并运行Lua code
int fret = luaL_dofile(L,"test.lua");
if(fret)
{
std::cout<<"read lua file error!"<<std::endl;
}
//关闭state
lua_close(L);
return 0;
}
四、小细节
关于lua.hpp
lua.hpp是我们在使用C++调用lua时要添加的头文件,它的内容如下:
// lua.hpp
// Lua header files for C++
// <<extern "C">> not supplied automatically because Lua also compiles as C++
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
其中,extern “C”的主要作用就是为了能够正确实现C++ 代码调用其他C语言代码。加上extern “C”后,会指示编译器这部分代码按c语言的进行编译,而不是C++ 的。由于C++ 支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。
这个功能十分有用处,因为在C++ 出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++ 中尽可能的支持C,而extern “C”就是其中的一个策略。
这个功能主要用在下面的情况:
1、C++ 代码调用C语言代码
2、在C++ 的头文件中使用
3、在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到
参考:
http://blog.csdn.NET/xiaoluoshan/article/details/53155758
http://www.jikexueyuan.com/course/36_6.html?ss=1
http://www.jellythink.com/archives/554
https://wenku.baidu.com/view/34f8f442a8956bec0975e307.html
http://blog.csdn.net/fengbangyue/article/details/7342045