C++与Lua初探
最近在工作中用到了Lua与C++的相互调用的知识,现在对两者的互相调用进行总结。
我觉得要了解两者之间的调用关系,需要弄清三件事情:
- lua栈是什么
- C++如何调用lua
- lua如何调用C++
在弄清这三件事情之前,让我们先来看看怎么讲Lua嵌入到C++程序中。
大致思路就是将Lua的源码打包成静态库,在程序编译的时候,将静态库也加入到编译选项中。
具体步骤如下:
- 去官网上下载Lua源码
- 在根目录下运行 make linux(由于我是在linux平台下编译的,所以make后面跟的是linux,直接运行make会有相关的提示)
- 运行之后会在 src 目录下,生成一个liblua.a的静态库,这个静态库是我们所需要的
- 在编译程序的时候加入这个静态库,同时可能还需要指定下C++源码中引用到lua的头文件路径,我在编译的过程中,发现lua的静态库还需要 libm.so 和 libdl.so 动态库,所以也需要指定下
Lua栈
Lua栈是C++和Lua交流的唯一途径,所有的信息交流都是通过这个栈来完成。所以在介绍两者具体的调用之前,非常有必要介绍一下这个栈。
首先看一下官方手册对这个栈的解释。
- Lua使用一个虚拟栈来和C互传值。栈上的每个元素都是一个Lua值(nil,数字,字符串,等等)
- 无论何时Lua调用C,被调用的函数都得到一个新的栈,这个栈独立于C函数本身的栈,也独立与之前的Lua栈。它里面包含了Lua传递给C函数的所有参数,而C函数则把要返回的结果放入这个栈以返回给调用者。
- 方便起见,所有针对栈的API查询操作都不严格遵循栈的操作规则,而是可以用一个索引来指向栈上的任何元素:正的索引指的是栈上的绝对位置(从1开始);负的索引则指从栈顶开始的偏移量。展开来说,如果堆栈有n个元素,那么索引1表示第一个元素(也就是最先被压栈的元素)而索引n则指最后一个元素;索引-1也是指最后一个元素(即栈顶的元素),索引-n是指第一个元素。
从官方手册中,我们可以对这个栈有了基本的认识。
下面的图可以从整体上认识C++和Lua是如何通过这个栈来进行交流的
下面的内容都需要联系到这张图,才能很好的说明
C++调用Lua
下面通过C++读取Lua全局变量和调用lua函数来说明下C++调用lua的过程
C++读取Lua全局变量(可以作为配置文件)
config.lua
--配置文件,包含两个全局变量
email = "xuqiangm@gmail.com"
blog = "xuqiangm@github.io"
config.c
#include <stdio.h>
#include <string.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
int main()
{
//初始化全局L
lua_State* L = luaL_newstate();
<span class="c1">//打开库
luaL_openlibs(L);
<span class="k">if</span><span class="p">(</span><span class="n">luaL_loadfile</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="s">"config.lua"</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"loading file failed.</span><span class="se">n</span><span class="s">"</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">lua_pcall</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> <span class="c1">//执行脚本
//从lua的全局表中获取变量blog的值,压入栈顶
lua_getglobal(L, "blog");
//查看栈顶是不是stirng类型的值,如果是则返回值,值依然留在栈中
char* name = luaL_checkstring(L, -1);
if(name != NULL)
{
printf("My blog is %sn", name);
}
<span class="n">lua_getglobal</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="s">"email"</span><span class="p">);</span>
<span class="kt">char</span><span class="o">*</span> <span class="n">email</span> <span class="o">=</span> <span class="n">luaL_checkstring</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">email</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Email is %s</span><span class="se">n</span><span class="s">"</span><span class="p">,</span> <span class="n">email</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">lua_close</span><span class="p">(</span><span class="n">L</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
}
可以看出 lua_getglobal 这个函数也是通过栈来将数据传给C++程序的
C++调用Lua函数
这一节我们来看一下C++怎么调用Lua中的函数的
add.lua
function add(x,y)
return x+y;
end
add.c
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
int main()
{
//初始化全局L
lua_State* L = luaL_newstate();
<span class="c1">//打开库
luaL_openlibs(L);
<span class="c1">//加载lua脚本文件,但不执行
if(luaL_loadfile(L, "add.lua"))
{
printf("loading file failed.n");
return 1;
}
<span class="n">lua_pcall</span><span class="p">(</span><span class="n">L</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">);</span> <span class="c1">//执行脚本
//从全局表中获取add函数,压入栈
//从这里,我猜想,在lua脚本中,函数会以function变量的形式存入全局表中
lua_getglobal(L, "add");
//压入参数,对应函数参数列表(参数顺序是从左到右)
lua_pushnumber(L, 1);
lua_pushnumber(L, 2);
<span class="c1">//调用这个函数,数字2表示有两个参数,数字1表示有一个返回值。
//这个函数会从栈中弹出三个元素,即add函数和两个参数
//会将一个返回值压入栈中
lua_pcall(L,2, 1,0);
<span class="c1">//从栈顶中获取值,即函数的返回值
int result = luaL_checknumber(L,1);
<span class="n">print</span><span class="p">(</span><span class="s">"result=%d</span><span class="se">n</span><span class="s">"</span><span class="p">,</span><span class="n">result</span><span class="p">);</span>
<span class="n">lua_close</span><span class="p">(</span><span class="n">L</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
}
从上面两个例子中我们可以看出Lua中的函数其实也是一种变量(function变量),这样上面两个例子就统一了。
Lua函数调用C++
add.lua
--调用C程序中的add函数
result = add(3 , 2)
print("result="..result)
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
int add(lua_State* L)
{
int a,b;
<span class="c1">//获取栈中的变量(其实是lua脚本中传进来的值)
a = luaL_checknumber(L, 1);
b = luaL_checknumber(L, 2);
<span class="c1">//将计算的结果压入栈(其实是将结果传给脚本)
lua_pushnumber(L, a+b);
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
}
int main()
{
//初始化全局L
lua_State* L = luaL_newstate();
<span class="c1">//打开库
luaL_openlibs(L);
<span class="n">lua_pushcfunction</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="n">add</span><span class="p">);</span>
<span class="n">lua_setglobal</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="s">"add"</span><span class="p">);</span>
<span class="c1">//加载lua脚本文件,但不执行
if(luaL_loadfile(L, "add.lua"))
{
printf("loading file failed.n");
return 1;
}
<span class="c1">//执行脚本
lua_pcall(L, 0, 0, 0);
<span class="n">lua_close</span><span class="p">(</span><span class="n">L</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
}
在上面这段代码中,有两个函数需要注意:
- lua_pushcfunction(L, add)。 首先我们看下lua手册中对这个函数的解释:
将一个 C 函数压栈。 这个函数接收一个 C 函数指针, 并将一个类型为 function 的 Lua 值压栈。 当这个栈顶的值被调用时,将触发对应的 C 函数。
具体在这段程序中,我将add函数的指针压入栈中,此时该函数指针处于栈位置
- lua_setglobal(L, “add”)。 我们还是先看下lua手册中对这个函数的解释:
从堆栈上弹出一个值,并将其设为全局变量 name (函数的第二个值)的新值。
联系lua_pushcfunction,我们可以知道,这个函数会将栈顶的add函数指针弹出,然后设置全局变量add的值为这个函数指针。这样在lua脚本中,就可以获取这个变量的值。
关于lua_State
对于lua_State,我在网上找到了一篇文章,对于这个类型解释挺不错,事实上,了解这个类型,对于C和Lua之间的关系就会有更透彻的了解,非常建议读一下。 lua 源码分析之线程对象lua_State