BeWithYou

胡搞的技术博客

  1. 首页
  2. C/C++/Lua
  3. Lua学习笔记(六)

Lua学习笔记(六)


C和Lua的交互

Lua是一门嵌入式的语言。它可以扩展,也可以被扩展。C和Lua交互有两种方式:

  • C作为宿主语言,Lua作为库,实现一些用C写起来很费力的操作。
  • Lua作为应用程序,调用C语言写的库,弥补Lua中功能性的缺失。

C和Lua交互用的部分成为C API。包括:读取Lua全局变量,调用Lua函数,运行Lua代码片段,注册C函数被Lua调用等。

C和Lua之间通信关键在于一个虚拟的栈。几乎所有的API调用都是针对占上的值进行操作,数据交换也是通过栈来完成。Lua以严格的LIFO规则操作栈。Lua中只能改变栈顶部分,C中可以修改任意位置。

重要的头文件

  • lua.h Lua基础函数库,提供lua_前缀的函数
  • lauxlib.h luaL_前缀,对lua_函数进行了封装
  • lualib.h 标准库

压入元素

void lua_pushnil (lua_State *L);
void lua_pushboolean (lua_State *L, int bool);
void lua_pushnumber (lua_State *L, double n);
void lua_pushlstring (lua_State *L, const char *s, size_t length);
void lua_pushstring (lua_State *L, const char *s);

上面是变量压栈,还有userdata和C函数的压栈。

压栈时注意栈内是否有足够的空间,起始时一般有20个空余槽位。必要时使用int lua_checkstack (lua_State *L, int sz);检测是否有足够的空间。

从栈中取值

int lua_toboolean (lua_State *L, int index);
double lua_tonumber (lua_State *L, int index);
const char * lua_tostring (lua_State *L, int index);
size_t lua_strlen (lua_State *L, int index);

即使元素类型不正确,也不会有问题。

lua_tostring函数返回一个指向字符串的内部拷贝的指针,它是const的。一个C函数返回后,Lua会清理他的私有栈,在此之前这个指针是有效的。所以 不要将指向Lua字符串的指针放到访问他们的内部函数中,会弄丢的。

使用lua_type()函数,判断栈中的元素类型。lua.h头文件中定义了每个类型对应的常量:LUA_TNIL、LUA_TBOOLEAN、LUA_TNUMBER LUA_TSTRING、LUA_TTABLE、LUA_TFUNCTION、LUA_TUSERDATA、LUA_TTHREAD。

其他栈操作

int lua_gettop (lua_State *L);
void lua_settop (lua_State *L, int index); 
void lua_pushvalue (lua_State *L, int index); 
void lua_remove (lua_State *L, int index); 
void lua_insert (lua_State *L, int index); 
void lua_replace (lua_State *L, int index);

lua_gettop返回栈中的元素个数,也是栈顶元素的索引。0表示栈空。举例:先后push进去a,b,c。栈顶元素为c正数索引为3,栈顶元素c负数索引为-1,lua_gettop得到3。

事实上,负数索引-x,其正数索引等于gettop-x+1。比如b的负数索引为-2,正数为gettop-2+1=3-2+1=2。

第一个入栈的元素索引是1,下一个是2。也可以用栈顶作参照,利用负数索引。栈顶的索引为-1,-2为前一个元素。

lua_settop设置栈顶,如果新的栈顶变低了,顶部的值会被抛弃。否则会压入n个nil值进去以达到需要的高度。使用lua_settop(L,0)清空栈。

弹出n个元素#define lua_pop(L,n) lua_settop(L, -(n)-1)

lua_pushvalue压入指定索引的一个拷贝到栈顶;lua/_remove移除指定索引的元素,上面的元素下移填补空白;lua/_insert移动栈顶元素到指定索引的位置;lua/_replace从栈顶弹出元素并且替换掉指定索引。

下面的操作对于栈的内容没有任何影响:

lua_settop(L, -1);
lua_insert(L, -1);

实际应用

Lua经常被当做配置文件供宿主语言使用。当然也可以用C调用lua函数。

不过更经常用到的是Lua中调用C函数,弥补功能性确实,比如luacjson等等。使用C完成这类功能比较复杂的任务,性能也比Lua强。

下面主要记录一下,C作为库函数扩展Lua的用法。

调用C函数

当Lua调用C函数时,使用和C调用Lua相同类型的栈来交互。C函数返回结果的个数。每个C函数有它自己的私有栈。Lua调用C函数的时候,第一个参数在私有栈的1号位置。反过来C调用Lua也是如此。

任何在Lua中注册的函数,必须用同样的原型: typedef int (*lua_CFunction) (lua_State* L); 在Lua中使用C函数,必须首先注册它。可以用lua_pushcfunction。但是更一般的方法,是定义一个C函数和其名字的结构体数组:

static const struct luaL_reg mylib[] = {
{"testfun",testfun},
 //...
{NULL,NULL}
};

最后一组必须以NULL来结尾,表示结束。

在Lua5.3中,我们使用luaL_newlib(L,mylib);来将函数列表注册到Lua中。

我们通常会将C写的库函数编译成so的形式供Lua调用。在C中,定义luaopen_开头+模块名的函数,Lua中把编译成的so引用进来。当执行到require "hello"的时候,会去寻找luaopen_hello的函数来执行,找不到会报错。

如果是require "world.hello",则去找luaopen_world_hello函数。

于是我们实现一个简单的例子:

//luacc.c
#include <stdio.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

static int add(lua_State* L)
{
 double op1 = luaL_checknumber(L,1);
 double op2 = luaL_checknumber(L,2);
 lua_pushnumber(L,op1+op2);
 return 1;
}
static luaL_Reg mylibs[] = {{"add",add},{NULL,NULL}};
int luaopen_luacc(lua_State *L)
{
 luaL_newlib(L,mylibs);
 lua_setglobal(L, "luacc");
 return 1;
}
require "luacc"
print(luacc.add(1,2)) -- 3.0

如果C中的luaopen_xxx函数里,不用lua_setglobal将模块设置到全局的话,在lua中可以这么使用local luacc = require "luacc"其实这么用更爽。

在macosx下编译,使用gcc -O2 -bundle -undefined dynamic_lookup -o luacc.so luacc.c

在centos下编译,使用gcc -g -O2 -Wall -fPIC --shared luacc.c -o luacc.so

如果需要用到C++的某些特性,则要修改相应的部分。

  • lua头文件部分需要extern "C" {}包起来
  • luaopen_xxx函数需要用extern "C" 修饰
回到顶部