Lua C接口编程(一)

简介: Lua C接口编程(一)

引言

skynet 和 openresty 都是深度使用lua的典范,学习lua不经要学会基本语法,还要学会C语言与Lua交互。lua的一大优点就是能和c/c++无缝连接,而且可以在不需要重复编译c/c++的情况下可以修改lua文件并且起作用,当我们的项目文件很大的时候,使用lua进行项目修改极大的减少了等待时间。

Lua由标准C编写而成,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,这使得Lua在应用程序中可以被广泛应用。

Lua C接口编程我会分为两个部分来介绍,第一部分介绍C/C++如何调用Lua,第二部分介绍Lua如何调用C/C++。

一、Lua虚拟栈

  • 栈中只能存放Lua类型的值,如果想保存C类型的值,需要先将C类型转换为Lua类型;
  • Lua每次调用C函数都会生成新的栈,独立于之前的栈
  • C在创建虚拟机是,伴随创建一个主协程和一个虚拟栈
  • 无论何时Lua再调用C时,只保证至少有LUA_MINSTACK大小的堆栈空间可以使用。LUA_MINSTACK一般定义为20,只要你不是一致压栈,通常不用担心堆栈大小。

Lua中的栈有两排索引,正数1索引的位置在栈底,负数索引-1在栈顶,这样做的好处是不需要知道栈的大小,只需要查找正负索引1的位置就能确定栈顶和栈底的位置。

注意:C 在调用lua的时候要注意虚拟堆栈的变化情况,要保持堆栈在函数执行前后不变,养成良好的习惯。

二、注册表

2.1 什么是注册表

有时候,我们需要在程序中使用一些非局部的变量。在C中我们可以使用全局变量或是静态变量来实现,而在为Lua编写C库的过程中,使用以上类型的变量并不是一个好的方式,原因如下:

  1. 这些变量中无法存储Lua的值
  2. 这些变量如果在多个Lua虚拟机中被使用,很可能造成不可预期的结果。

一个替代方案是,将这些值存储在Lua的全局变量中。这种方式解决了上面提到的两个问题,Lua全局变量可以存储任何Lua的值,同时每一个Lua状态机都有自己独立的一套全局变量。但这依旧不是最好的方式,因为是Lua的全局变量,Lua程序可以随意的修改变量的值,这很可能对C库中的函数在使用这些变量时造成影响。

为了进一步避免上述情况,Lua提供了一张特殊的表(table),它可以供C代码随意使用。但是对于Lua代码,访问却是被禁止的。这个特殊的表便是注册表。

2.2 伪索引 pseudo-index

伪索引跟虚拟栈中正常的索引类似,区别在于,虽然使用它也是通过虚拟栈,但其所对应的值并不是存储在虚拟栈中。比如:LUA_REGISTRYINDEX 就是一个伪索引,定义在”lua.h”中。它用于通过虚拟栈访问注册表(但注册表并非实际存储在虚拟栈中),使用时按照虚拟栈中正常索引的使用方式使用。

2.3 小结

用来在多个C库中共享Lua数据,包括userdata 和 lightuserdata等;

  • 一张预定义的表,用来保存任何C代码想保存的Lua值;
  • 使用 LUA_REGISTRYINDEX来索引;
  • 比如:获取skynet_context

三、Lua 常用API介绍

lua_State* L=luaL_newstate(); -- 函数返回一个指向堆栈的指针
luaL_openlibs(L);        -- 打开指定状态机中的所有 Lua 标准库,也就是把所有标准类库加载到指定的虚拟机.
lua_createtable(L,0,0);  -- 新建并压入一张表
lua_pushstring(L,0,0);  -- 压入一个字符串
lua_pushnumber(L,0,0);  -- 压入一个数字
lua_tostring(L,1);    -- 取出一个字符串
lua_tointeger(L,1);   -- 取出数字
double b =lua_tonumber();  -- 取出一个double类型的数字
lua_load()函数        -- 当这个函数返回0时表示加载
luaL_loadfile(filename)    -- 这个函数也是只允许加载lua程序文件,不执行lua文件。它是在内部去用lua_load()去加载指定名为filename的lua程序文件。当返回0表示没有错误。
luaL_dofile()          -- 这个函数不仅仅加载了lua程序文件,还执行lua文件。返回0表示没有错误。
lua_push*(L,data)          -- 压栈,
lua_to*(L,index)           -- 只取值, 不出栈
lua_pop(L,count)           -- 出栈。
lua_close(L);              -- 释放lua资源
lua_call(L,0,0,0);         -- 调用对应函数
lua_pcall(L,0,0,0);        -- 安全的调用对应函数,最后一个参数是错误处理函数

除了上面的API,我们着重看下面几个API:

  1. lua_getglobal(L, funcname)
    lua_getglobal是个宏,每次调用这个宏的时候,都会将Lua代码中funcname与之相应的全局变量值压入栈中。
  2. lua_setfield(L, LUA_REGISTRYINDEX, “LUA_NOENV”);
    等价于执行 LUA_REGISTRYINDEX[LUA_NOENV] = stackTopElem 操作, 其中 stackTopElem指当前栈顶元素。
  3. lua_getfield(L, LUA_REGISTRYINDEX, “LUA_NOENV”);
    将LUA_REGISTRYINDEX[LUA_NOENV] 压入栈,返回入栈值的类型
  4. int luaL_ref(lua_State L, int t)
    将栈顶的元素出栈作为value,从虚拟栈的索引 t 处获取到 table ,之后相当于执行"table[key] = value"的操作。函数返回生成的唯一的key,程序可以通过该key获取到对应的value。如果 value 为 nil ,则不会生成key,函数返回 LUA_REFNIL。
  5. void luaL_unref(lua_State L, int t, int ref);
    从虚拟栈的索引 t 处获得 table,之后做等价于执行 table[ref] = nil 操作。如果 ref 是LUA_NOREF或LUA_REFNIL,则函数不做任何操作。
  6. lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
    从注册表通过key(ref)获取到对应的value(foo函数),并将其入栈

使用 luaL_ref 和 luaL_unref 时无需关心如何创建唯一的”key”,便可以在注册表中自由的存取数据。

代码示例

示例1:常用api用法

C源文件test_field.c

#include <stdio.h>
#include <stdlib.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
/*
    Lua的异常捕获主要基于pcall及xpcall函数。
*/
void getStackSize(const char * desc, const int count)
{
    printf("%s stack size = [ %d ]\n",desc, count);
}
void test_api_getfield()
{
    lua_State *L = luaL_newstate();
    // 加载并执行目标lua文件
    if ( LUA_OK != luaL_dofile(L, "test_field.lua") ) {
        const char* err = lua_tostring(L, -1);
        fprintf(stderr, "err:\t%s\n", err);
        return ;
    }
    lua_getglobal(L,"tab");           // 查找 tab 变量压入栈底
    // lua_gettop 获取栈中元素数量
    getStackSize("stage 1", lua_gettop(L));
    // 
    lua_getfield(L, -1, "a");           // 将 tab.a 入栈
    int nTab_a = lua_tointeger(L,-1); // 将 tab.a 取出赋值给变量nTab_a
    lua_getfield(L, -2, "b");           // 将 tab.b 入栈
    int nTab_b = lua_tointeger(L,-1); // 将 tab.b 取出赋值给变量nTab_b
    getStackSize("stage 2",lua_gettop(L));
    lua_pop(L, 3);                  // 清除掉栈中多余的3个变量tab、tab.a、tab.b
    getStackSize("stage 3",lua_gettop(L));
    int nTab_c = 2 * nTab_a + nTab_b;
    lua_pushinteger(L, nTab_c);       // 将 c = 2a + b 计算完成,压入栈顶
    printf("nTab_c = %d \n", nTab_c);  // 输出: 5
    getStackSize("stage 4",lua_gettop(L));
    lua_getglobal(L,"lua_func");        // 查找lua_func函数并将其压入栈底
    lua_pushinteger(L, 3);              // 压入函数变量 x=3  
    getStackSize("stage 5",lua_gettop(L));
    // lua_pcall 在保护模式下调用一个函数,防止异常程序直接退出
    lua_pcall(L,1,1,0);             // 执行脚本函数lua_func
    getStackSize("stage 6",lua_gettop(L));
    int result = lua_tointeger(L,-1);   // 从栈中取回返回值 
    getStackSize("stage 7",lua_gettop(L));
    printf("res = %d \n", result);  
    lua_pop(L,1);                       // 弹出返回结果
    getStackSize("stage 8",lua_gettop(L));
    lua_close(L);                       //关闭lua环境  
}
int main(int argc, char** argv)
{
    test_api_getfield();
    return 0;
}

lua文件test_fielf.lua

-- tab 表
tab =
{
    a = 2,
    b = 1
}
-- 全局变量c
c = 100;
function lua_func(x)
    -- print("lua c: ", c)
    return (tab.a * x * x + tab.b * x + c)
end

编译方法:

gcc -g -o test_field test_field.c -llua -ldl -lm

运行结果:

示例2:luaL_ref 、luaL_unref、lua_rawgeti 应用

C源文件test_reg_ref.c

#include <stdlib.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
int main()
{
    lua_State *L = luaL_newstate();  
    luaL_openlibs(L);
    if ( LUA_OK != luaL_dofile(L, "test_reg_ref.lua") ) {
        const char* err = lua_tostring(L, -1);
        fprintf(stderr, "err:\t%s\n", err);
        return 1;
    }
    lua_getglobal(L,"foo");
    printf("stack 1 size : %d, %d\n", lua_gettop(L), lua_type(L,-1));
    // 从堆栈顶弹出foo函数,存放到注册表中并返回引用
    // luaL_ref 返回一个int的值。这个返回值就是对应的foo函数的 key  
    int ref =  luaL_ref(L, LUA_REGISTRYINDEX);
    printf("stack 2 size : %d\n", lua_gettop(L));
   // lua_rawgeti(L,LUA_REGISTRYINDEX,ref); 可以从注册表通过key(ref)获取到对应的value(foo函数)
   // 将该函数放入堆栈
    lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
    printf("stack 3 size : %d, %d\n", lua_gettop(L), lua_type(L,-1));
    // 调用foo函数后 foo出栈
    lua_pcall(L,0,0,0);    
    printf("stack 4 size : %d\n", lua_gettop(L));
    printf("----------------分割线 1------------------\n");
    // 继续将foo压入堆栈
    lua_getglobal(L,"foo");  
    printf("stack 5 size : %d\n", lua_gettop(L));
    lua_setfield(L, LUA_REGISTRYINDEX, "fooKey");  //  相当于:LUA_REGISTRYINDEX[fooKey] = foo;
    printf("stack 6 size : %d\n", lua_gettop(L));
    lua_getfield(L, LUA_REGISTRYINDEX, "fooKey");  // 通过 fooKey 从 LUA_REGISTRYINDEX拿到foo 并入压栈
    printf("stack 7 size : %d, %d\n", lua_gettop(L), lua_type(L,-1));
    lua_pcall(L,0,0,0);        // foo call
    printf("stack 8 size : %d, %d\n", lua_gettop(L), lua_type(L,-1));
    printf("----------------分割线 2------------------\n");
    // 一旦ref在注册表的引用解除,就无法继续通过key(ref)这个引用获取到 value(即foo函数)
    luaL_unref(L, LUA_REGISTRYINDEX, ref);
    // 从注册表解除后,lua_rawgeti无法再通过key(ref) 找到foo
    lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
    printf("stack 9 size : %d, %d\n", lua_gettop(L), lua_type(L,-1));
    // 通过 fooKey 还是可以继续从LUA_REGISTRYINDEX拿到foo 并入压栈
    lua_getfield(L,LUA_REGISTRYINDEX,"fooKey");
    printf("stack 10 size : %d\n", lua_gettop(L));
    lua_pcall(L,0,0,0);
    printf("---------------- end -----------------\n");
    lua_close(L);
   return 0;
}

Lua文件test_reg_ref.lua

function foo()
    print("test_reg_ref foo call")
end

编译方法:

gcc -g -o test_reg_ref test_reg_ref.c -llua -ldl -lm

运行结果:


推荐一个零声学院免费教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,点击立即学习:

相关文章
|
3月前
Lua语法(六)——面相对象编程
Lua语法(六)——面相对象编程
33 0
|
6月前
|
算法 NoSQL Java
springboot整合redis及lua脚本实现接口限流
springboot整合redis及lua脚本实现接口限流
266 0
|
6月前
|
消息中间件 Kubernetes NoSQL
Lua C接口编程(二)
Lua C接口编程(二)
分布式接口幂等性、分布式限流(Guava 、nginx和lua限流)
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条,这就没有保证接口的幂等性。
|
编译器 Linux C语言
lua编程基础
lua编程基础
108 0
|
JSON 数据格式
wrk post lua脚本取excel参数压力测试,判断接口性能
wrk post lua脚本取excel参数压力测试,判断接口性能
222 0
|
NoSQL 应用服务中间件 API
【精选】Nginx模块Lua-Nginx-Module学习笔记(一)Nginx Lua API 接口详解
源码地址:https://github.com/Tinywan/Lua-Nginx-Redis 一、介绍   各种* _by_lua,* _by_lua_block和* _by_lua_file配置指令用作nginx.conf文件中Lua API的网关。
2385 0