最近在工作中用到了Lua与C++的相互调用的知识,现在对两者的互相调用进行总结。

我觉得要了解两者之间的调用关系,需要弄清三件事情:

  1. lua栈是什么
  2. C++如何调用lua
  3. 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">&#34;config.lua&#34;</span><span class="p">))</span>
<span class="p">{</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">&#34;loading file failed.</span><span class="se">n</span><span class="s">&#34;</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">&#34;email&#34;</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">&#34;Email is %s</span><span class="se">n</span><span class="s">&#34;</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">&#34;result=%d</span><span class="se">n</span><span class="s">&#34;</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">&#34;add&#34;</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

参考资料