c api

lua是一种嵌入式语言,可以链接到其他车型的库,lua库看拓展内容 使用了lua的程序可以注册其他语言的函数来向lua 添加功能

c api 是一组能是c代码与lua 交换的函数, 包括 读写lua全局变量, 调用lua函数 ,运行lua 代码 注册 c函数给lua调用

lua 与 c 通信的主要方法是一个无处不在的虚拟栈, 所有api都会操作这个栈上的值,所有交换都在c与lua的差异都在栈上解决

c 中调用lua

#include<stdio.h>
#include<string.h>
#include<lua.h>
#include<luaxlib.h>
#include<lualib.h>

int main(void)
{
    char buff[256];
    int error;
    lua_State* L = luaL_newstate() /*打开lua*/
    luaL_openlibs(L); /* 打开标准库 */ 
    while(fgets(buff, sizeof(buff), stdin) != NULL)
    {
        /* pcall 调用lua 函数*/
        error = luaL_Loadbuffer(L, buff, strlen(buff), "line") || lua_pcall(L, 0, 0, 0);
        if(error)
        {
            fprintf(stderr, "%s", lua_tostring(L, -1));
            lua_pop(L, 1);  /*从栈上获取错误信息*/
        }
    }
    lua_close(L);
    return 0;
}

lua.h
提供创建lua环境调用lua函数的函数 读写lua 环境中的全局变量以及注册c函数给lua调用,以lua_为前缀

luaxlib.h 定义了辅助库,以luaL_ 为开头, lua 库没有全局变量 都在一个 lua_State 之中 所有的api都要传入该结构体

luaL_newState() 创建了一个新的lua 环境,其中没有任何函数(print都没有)需要 luaL_openlib() 来打开标准库

创建好state 之后 可以使用luaL_loadbuffer 来编译输入内容,并想栈压入程序块,任何调用lua_pcall
如果发生错误会向栈压入错误信息,可以使用lua_string()来获取 并打印,最后调用lua_pop 删除信息


lua 与c++直接数据交换需要考虑类型,内存管理的区别
lua使用了一个抽象的来保存数据,栈上保存任何类型的lua的值

———————->

先进先出

压入栈

c类型都有一个压栈函数

// c 向lua 栈添加元素
void lua_pushnil(lua_State* L);
void lua_pushboolean(lua_State* L, int bool);
void lua_pushnumber(lua_State* L, lua_Number n);//double 类型
void lua_pushinteger(lua_State* L, lua_Integer n);
void lua_pushlstring(lua_State* L, const char* s, size_t len);//任意长度的字符串
void lua_pushstring(lua_State* L, const char* s);//以0结尾的字符串

栈至少会有20 个空槽
lua_checkstack()检查栈是否有足够的空间

查询元素

lua api 使用整数索引来引用栈元素,第一个压入的元素索引是1 第二个为2 知道栈顶 也可以以栈顶为开始以负数为为索引来查询元素
-1 表示栈顶, -2表示下面的一个

调用lua_tostring(L, -1)会将栈顶元素作为字符串返回

使用lua_is*(lua_State* L, int index) 来检测类型
*代表number string table等

isnumber 和isstring 会检测lua变量能否转化为对应类型 使用对于数字 isstring 为真

以下函数来获取栈内元素

LUA_API lua_Number      (lua_tonumber) (lua_State *L, int idx);
LUA_API lua_Integer     (lua_tointeger) (lua_State *L, int idx);
LUA_API int             (lua_toboolean) (lua_State *L, int idx);
LUA_API const char     *(lua_tolstring) (lua_State *L, int idx, size_t *len);
LUA_API size_t          (lua_objlen) (lua_State *L, int idx);
LUA_API lua_CFunction   (lua_tocfunction) (lua_State *L, int idx);
LUA_API void           *(lua_touserdata) (lua_State *L, int idx);
LUA_API lua_State      *(lua_tothread) (lua_State *L, int idx);
LUA_API const void     *(lua_topointer) (lua_State *L, int idx);
其余栈操作
int lua_gettop(lua_State* L); // 返回栈中元素个数 注意是个数也就是栈顶的索引 具体值使用上面函数获取
void lua_settop(lua_State* L, int index);// 修改栈顶索引也就是修改栈内元素数量 这样的修改会丢去多余 及补充nil
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的情况

作为配置

luaL_loadfile(Lua_State* L, const char* fname); // 加载lua文件
lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);// 执行lua 如果发生错误会把一个错误消息压入栈给c api获取处理
lua_getglobal(L, fieldName)// 获取lua全局变量压入栈 会将lua的全局变量压入栈并给c pi调用

加载并且读取lua 程序使宿主程序可以不修改而达到目的, 同时lua作为编程语言还可以执行逻辑运算等,可以让配置更清晰方便

table操作

lua table 操作函数只有一个lua_gettable() 需要知道table的位置,以及key,就能压入value 弹出key

background = { r = 255, g = 144, b = 122 }
以下函数假定目标table 在栈顶(-1位置)
//对于5.1之后的lua 获取table的操作可以简化为
//获取table属性值
lua_getfield(L, -1, key); //字符串没有压入栈所以 -1 是table的索引
//等价于
lua_pushstring(L, key); 
lua_gettable(L, -2);
/*****************/
// 修改table属性值
lua_pushnumber(L, value);
lua_setfield(L, -2, index);// 会弹出value
// 等价于
lua_pushstring(L, index);
lua_pushnumber(L, value);
lua_settable(L, -3);
// 对于不同类型只需要使用对应的函数替代即可

创建一个lua table

lua_newtable(L);// 创建table
lua_pushnumber(L, value); 
lua_settable(L, -2, index);// 创建table属性
...
lua_setglobal(L, tName);// 赋值到 lua全局变量会弹出table
调用lua函数

c调用lua函数只需要将函数载入到栈中并压入参数即可完成调用

lua_getglobal(L, "f"); // 全局变量中获取函数变量并且压入栈
lua_pushnumber(L, x); // 压入一个参数 
lua_pushnumber(L, y); // 压入第二个参数
// 调用lua函数, 2个参数 || 1个返回值 || 错误处理函数索引 栈中的函数索引 0代表没有
if(lua_pcall(L, 2, 1, 0) != 0) // 此调用会弹出函数及参数
    error(L, "error running function %s", lua_tostring(L, -1));
// 查看函数调用结果
if(!lua_isnumber(L, -1))
    error(L, "function return error type")
int z = lua_tonumber(L, -1);
lua_pop(L, 1); // 弹出返回值
return z;

从lua到c

使用lua来拓展程序,还可以将c的函数注册到lua中

lua调用c也使用了一个c调用lua相同的栈,c函数从栈获取函数参数,并且将结果压入栈,以及结果数量,

栈不是全局的结构,每个函数都有自己的局部私有栈,lua调用c时第一个参数总是局部栈的索引1,

c函数

所有的注册到lua的c函数都有相同的原型,

static int l_sin(lua_State* L)
{
    double d = luaL_checknumber(L, 1); // get arg with type check
    lua_pushnumber(L, sin(d)); // push return 
    return 1;
}

typedef int (*lua_CFunction) (lua_State* L);  // 返回值表示压入栈的返回值数量, 函数返回后会自动删除栈中结果

// 注册函数
lua_pushcfunction(L, l_sin);
// 添加为全局变量
lua_setglobal(L, "mysin");

c 模块 (注册到lua module)

// 定义函数
static int l_dir(lua_State* L){
    //...
}
static const struct lua_Reg mylib[] = 
{
    //FUNC NAME func pointer 
    {"dir", l_dir},
    {NULL, NULL},
}
// 注册module到lua
luaL_register(L, "mylib", myLib);

最后将c代码编译成动态链接库加入path之中即可使用
在lua中只要使用require就可以获取对应module的table

require “mylib”

数组操作

数组是编程中使用最广泛的数据结构,为了方便与性能,lua为数组提供了专用函数,在lua 数组就是table

//                      table 栈上位置  table的索引
void lua_rawgeti(lua_State* L, int index, int key);
//<==>
lua_pushnumber(L, key); // 压入table索引值
lua_rawget(L, t);// 从栈上的位置找到table 并且以栈顶的索引访问table并且返回值

void lua_rawseti(lua_State* L, int index, int key);
//<==>
lua_pushnumber(L, key);// 压入table索引值
lua_insert(L, -2);// 把key插入到前值之后,
lua_rawset(L, t)// 插入table

//example
int l_map(lua_State* L)
{
    int i, n;
    lua_checktype(L, 1, LUA_TTABLE);
    lua_checktype(L, 2, LUA_TFUNCTION);
    n = lua_objlen(L, 1);// get table size
    for(i = 1, i <= n; i++)
    {
        lua_pushvalue(L, 2);//压入function 
        lua_rawgeti(L, 1, i);// 获取到table的成员t[i]并压入栈
        lua_call(L, 1, 1);//调用 function(t[i])
        lua_rawseti(L, 1, i);// t[i] = call result
    }
    return 0;// 函数不向lua返回值
}

总结

c 与lua的交互都在一个栈上,c调用lua根据栈上值的信息完成读取lua数据,调用lua函数等操作,lua调用c主要是绑定函,从栈获取参数并将结果压入栈。