From: http://blog.chinaunix.net/uid-52437-id-2108806.html
例四,在Lua代码中调用C++函数
能Lua代码中调用C函数对Lua来说至关重要,让Lua能真正站到C这个巨人的肩膀上。要写一个能让Lua调用的C函数,就要符合 lua_CFunction 定义: typedef int (*lua_CFunction) (lua_State *L);
当Lua调用C函数的时候,同样使用栈来交互。C函数从栈中获取她的参数,调用结束后将结果放到栈中,并返回放到栈中的结果个数。
这儿有一个重要的概念:用来交互的栈不是全局栈,每一个函数都有他自己的私有栈。当Lua调用C函数的时候,第一个参数总是在这个私有栈的index=1的位置。
// a.cpp
#include <iostream>
#include <lua.hpp>
#include <complex>
using namespace std;
//C函数,做复数计算,输入实部,虚部。输出绝对值和角度
int calcComplex(lua_State *L)
{
//从栈中读入实部,虚部
double r = luaL_checknumber(L, 1);
double i = luaL_checknumber(L, 2);
complex<double> c(r, i);
//存入绝对值
lua_pushnumber(L, abs(c));
//存入角度
lua_pushnumber(L, arg(c)*180.0/3.14159);
return 2;//两个结果
}
int main()
{
const char *szLua_code =
"v,a = CalcComplex(3,4) "
"print(v,a)";
lua_State *L = luaL_newstate();
luaL_openlibs(L);
//放入C函数
#if 0
lua_pushcfunction(L, calcComplex);
lua_setglobal(L, "CalcComplex");
#else
lua_register(L, "CalcComplex", calcComplex); // 等价于上两句
#endif
//执行
bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
if(err)
{
cerr << lua_tostring(L, -1);
lua_pop(L, 1);
}
lua_close(L);
return 0;
}
结果返回 5 53.13... ,和其它数据一样,给Lua代码提供C函数也是通过栈来操作的,因为 lua_pushcfunction 和 lua_setglobal 的 组合很常用,所以Lua提供了一个宏:
这两句代码也就可写成 lua_register(L,"CalcComplex",calcComplex);
闭包(closure)
在编写用于Lua的C函数时,我们可能需要一些类似于面向对象的能力,比如我们想在Lua中使用象这样的一个计数器类:- struct CCounter{
- CCounter()
- :m_(0){}
- int count(){
- return ++i;
- }
- private:
- int m_;
- };
这时我们就需要一种机制让数据与某个函数关联,形成一个整体,这就是Lua中的闭包,而闭包里与函数关联的数据称为 UpValue 。
使用Lua闭包的方法是定义一个工厂函数,由它来指定UpValue的初值和对应的函数,如:
// a.cpp
#include <iostream>
#include <lua.hpp>
using namespace std;
//计算函数
int count(lua_State *L)
{
//得到UpValue
double m = lua_tonumber(L, lua_upvalueindex(1));
//更改UpValue
lua_pushnumber(L, ++m);
lua_replace(L, lua_upvalueindex(1));
//返回结果(直接复制一份UpValue作为结果)
lua_pushvalue(L, lua_upvalueindex(1));
return 1;
}
//工厂函数,把一个数字和count函数关联打包后返回闭包。
int newCount(lua_State *L)
{
//计数器初值(即UpValue)
lua_pushnumber(L,0);
//放入计算函数,告诉它与这个函数相关联的数据个数
lua_pushcclosure(L, count, 1);
return 1;//一个结果,即函数体
}
int main()
{
const char *szLua_code =
"c1 = NewCount() "
"c2 = NewCount() "
"for i=1,5 do print(c1()) end "
"print('---------------') "
"for i=1,5 do print(c2()) end";
lua_State *L = luaL_newstate();
luaL_openlibs(L);
// 注册C函数
lua_register(L, "NewCount", newCount);
//执行
bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
if(err)
{
cerr << lua_tostring(L, -1);
lua_pop(L, 1);
}
lua_close(L);
return 0;
}
执行结果是:
[zcm@vm-fedora20 luaCallClass]$ make
g++ -o a a.cpp -Wall -Os -std=c++11 -llua
[zcm@vm-fedora20 luaCallClass]$ ./a
1
2
3
4
5
---------------
1
2
3
4
5
可以发现这两个计算器之间没有干扰,我们成功地在Lua中生成了两个“计数器类”。
这里的关键函数是 lua_pushcclosure ,她的第二个参数是一个基本函数( 例子中是count ),第三个参数是 UpValue 的个数( 例子中为 1 )。在创建新的闭包之前,我们必须将关联数据的初始值入栈,在上面的例子中,我们将数字0作为初始值入栈。如预期的一样, lua_pushcclosure 将新的闭包放到栈内,因此闭包作为newCounter的结果被返回。
实际上,我们之前使用的 lua_pushcfunction 只是 lua_pushcclosure 的一个特例:没有 UpValue 的闭包。查看它的声明可 以知道它只是一个宏而已:
#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)
在count函数中,通过 lua_upvalueindex(i) 得到当前闭包的 UpValue 所在的索引位置,查看它的定义可以发现它只是一个简单的 宏:
#define lua_upvalueindex(i) (LUA_GLOBALSINDEX-(i))
宏里的 LUA_GLOBALSINDEX 是一个伪索引,关于伪索引的知识请看下节
伪索引
伪索引 除了它对应的值不在栈中之外,其他都类似于栈中的索引。Lua C API中大部分接受索引作为参数的函数,也都可以接受假索引作为参数。伪索引 被用来访问线程的环境,函数的环境,Lua注册表,还有C函数的UpValue。
- 线程的环境(也就是放全局变量的地方)通常在伪索引 LUA_GLOBALSINDEX 处。
- 正在运行的 C 函数的环境则放在伪索引 LUA_ENVIRONINDEX 之处。
- LUA_REGISTRYINDEX则存放着Lua注册表。
- C函数UpValue的存放位置见上节。
LUA_GLOBALSINDEX 位置上的table存放着所有的全局变量,比如这句
lua_getfield(L, LUA_GLOBALSINDEX, varname);
就是取得名为varname的全局变量,我们之前一直使用的 lua_getglobal 就是这样定义的: #definelua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s))
下面的代码利用 LUA_GLOBALSINDEX 得到所有的全局变量
- int main()
- {
- char *szLua_code =
- "a=10 "
- "b=\"hello\" "
- "c=true";
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- //执行
- bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- cerr << lua_tostring(L, -1);
- lua_pop(L, 1);
- }
- else
- {
- //遍历LUA_GLOBALSINDEX所在的table得到
- lua_pushnil(L);
- while(0 != lua_next(L,LUA_GLOBALSINDEX))
- {
- // 'key' (在索引 -2 处) 和 'value' (在索引 -1 处)
- /*
- 在遍历一张表的时候,不要直接对 key 调用 lua_tolstring ,
- 除非你知道这个 key 一定是一个字符串。
- 调用 lua_tolstring 有可能改变给定索引位置的值;
- 这会对下一次调用 lua_next 造成影响。
- 所以复制一个key到栈顶先
- */
- lua_pushvalue(L, -2);
- printf("%s - %s ",
- lua_tostring(L, -1), //key,刚才复制的
- lua_typename(L, lua_type(L,-2))); //value,现在排在-2的位置了
- // 移除 'value' 和复制的key;保留源 'key' 做下一次叠代
- lua_pop(L, 2);
- }
- }
- lua_close(L);
- return 0;
- }
LUA_REGISTRYINDEX 伪索引处也存放着一个table,它就是Lua注册表(registry)。这个注册表可以用来保存任何C代码想保存 的Lua值。
加入到注册表里的数据相当于全局变量,不过只有C代码可以存取而Lua代码不能。因此用它来存储函数库( 在下一节介绍 )中的一些公共变量再好不过了。