Lua进阶用法之Lua和C的接口设计

一:lua/c的接口编程

首先skynet、openresty 都是深度使用 lua 语言的典范;学习 lua 不仅仅要学习基本用法,还要学会使用 c 与 lua 交互,这样才学会了 lua 作为胶水语言的精髓,下面看一下他们两个的调用过程。

虚拟栈:

1.栈中只能存放 lua 类型的值,如果想用 c 的类型存储在栈中,需要将 c 类型转换为 lua 类型;

2.lua 调用 c 的函数都得到一个新的栈,独立 于之前的栈;

3.c 调用 lua,每一个协程都有一个栈;一个 lua 虚拟机中可以有多个协程,但同时只能有一个协程在运行;

4.c 创建虚拟机时,伴随创建了一个主协程,默认创建一个虚拟栈;

5.无论何时 Lua 调用 C , 它都只保证至少有 LUA_MINSTACK 这么多的堆栈空间可以使用。 LUA_MINSTACK 一般被定义为 20 ,因此,只要你不是不断的把数据压栈, 通常你不用关心堆栈大小。

对于Lua调CC调Lua他俩的情况是不同的,我们在Lua调C的过程中,我们不需要去维护这个创建的栈,因为一旦调用 c 的函数都会得到一个新的栈,并且系统会自动释放栈的空间,所以不需要我们去维护。但是对于C调Lua来说的话,需要我们去维护这个栈的大小,避免过大。

闭包和上值:

对于闭包和上值对于Lua来说还是很重要的,大家可以看看这位博主的讲解,十分清楚。

Lua闭包和Upvalue上值-CSDN博客

二:Lua和C的具体代码实现

我们上面说了我们会C调Lua,然后Lua再调C,因此我们将他们都拆开一个一个查看:

1:C调用Lua

C和Lua的区别:我们一旦修改C的代码,那么我们就要重新进行编译,才能进行使用,而Lua不同,它不需要进行重新编译,只需要重新启动一下即可使用,因此对于一些场景来说,我们一些固定的场合使用C语言来实现,而经常改动的比如游戏代码,我们可以使用Lua来写,我们就不用每次都进行编译浪费时间了,这样极大的提高了写代码的效率。因此我们如果在代码中实现文件的重新启动,那么我们就实现了热更新操作。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

//因为Lua是动态语言,我们可以实现热更新
//c调用Lua函数
static void
call_func_0(lua_State *L, const char* funcname)
{
    lua_getglobal(L, funcname);     //从Lua中的全局函数,找到这个名称的函数
    lua_pushinteger(L, 1);          // 压入一个整数到栈顶
    lua_call(L, 1, 0);              // 调用函数,参数为1,返回值为0
}

//通过c调用LUa的函数
//c调用Lua的话,我们需要维护这个栈个数,因为c调用Lua是同时只有一个协程在工作,所以只有一个栈
//而Lua调c,就不需要维护,只需要注意别一直压栈就可以了,因为Lua每调用一次,就会重新生成一个新的栈。
int main(int argc, char** argv)
{
    lua_State *L = luaL_newstate();     // 初始化lua虚拟机
    luaL_openlibs(L);                   // 加载lua标准库
    if (argc > 1) {                     // 加载lua脚本

        lua_pushboolean(L, 1);          // 压入一个布尔值到栈顶
        lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");        // 把栈顶的值设置到注册表中,可以在多个C库中共享数据

        if ( LUA_OK != luaL_dofile(L, argv[1]) ) {              // 加载lua脚本到虚拟机中
            const char* err = lua_tostring(L, -1);              // 获取错误信息
            fprintf(stderr, "err:\t%s\n", err);                 
            return 1;
        }

        call_func_0(L, "Init");                //开始调用Lua的代码
        call_func_0(L, "Loop");
        call_func_0(L, "Release");
        lua_close(L);
    }

    return 0;
}

2:被C调用的Lua代码

Lua 复制代码
package.cpath = "luaclib/?.so"
local so = require "uv.c"        --这里准备调用C语言中的库函数

--这里又通过Lua调用C的函数
function Init(args)
    print("call [init] function", args)
end

function Loop()
    print("call [loop] function")
    for k, v in ipairs({1,2,3,4,5}) do
        so.echo(v)        --这个echo函数就是通过调用C库里的函数,也就是Lua调用C
    end
end

function Release()
    print("call [release] function")
end

在讲被Lua调用的C库的时候,我们先看一下Lua是怎么查找C库中的函数的。在下面的代码中,类似于C中的#include,用来加载头文件的,

Lua 复制代码
local so = require "tbl.c"        --这里准备调用C语言中的库函数

当Lua找到此代码后从C库中开始查找luaopen_tbl_c,因此他是有查找的规则的。在下面有讲到。

cpp 复制代码
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>

//我们C写的函数
static int
lecho (lua_State *L) {
    const char* str = lua_tostring(L, -1);  
    fprintf(stdout, "%s\n", str);
    return 0;
}

static const luaL_Reg l[] = {// 导出给lua使用数组
	{"echo", lecho},        //K:函数名,V:函数指针,存放我们自己的函数名
	{NULL, NULL},
};

//相当于创建一个表(或者叫栈),然后将变量或者函数压入栈中,后面的数字为索引
//命名规则:luaopen_模块名,当Lua加载C库时,会先搜索这个函数,然后调用这个函数,返回一个表,然后把这个表赋值给模块名,然后就可以使用了。
int
luaopen_tbl_c(lua_State *L) { // local tbl = require "tbl.c"
    // 创建一张新的表,并预分配足够保存下数组 l 内容的空间
	// luaL_newlibtable(L, l);
	// luaL_setfuncs(L, l, 0);      //注册函数到表中,0表示函数表的索引为0

    // luaL_newlib 函数可以创建一个新的表,然后把函数表 l 中的函数注册到表中,然后把表返回给Lua
    luaL_newlib(L, l);      // 等价于上面两行
	return 1;
}

3:被Lua调用的C库

我们正常的函数,当使用完,他的内存会被回收,里面的变量的值也会消失,但是闭包不同,他还是保存变量的值。并且在这个函数中共享。

cpp 复制代码
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
// 闭包实现:  函数 + 上值  luaL_setfuncs
// lua_upvalueindex(1)
// lua_upvalueindex(2)

//闭包可以在函数内部进行数据共享,因为上值是大家都可以使用的
static int
lecho (lua_State *L) {
	lua_Integer n = lua_tointeger(L, lua_upvalueindex(1));      // 取出上值,然后把栈顶的元素替换掉上值
    n++;
    const char* str = lua_tostring(L, -1);     //-1 表示栈顶的元素     
    fprintf(stdout, "[n=%lld]---%s\n", n, str);     //输出到标准输出
    lua_pushinteger(L, n);                      // 把n压入栈顶
    lua_replace(L, lua_upvalueindex(1));        // 把栈顶的元素替换成上值
    return 0;
}

static const luaL_Reg l[] = {
	{"echo", lecho},
	{NULL, NULL},
};

//我们压入这个栈之后,变量是在栈中的,所以我们可以把这个栈中的值保存下来,然后在函数中使用,但是是看不到的,所以我们需要使用上值
//命名规则:luaopen_模块名,当Lua加载C库时,会先搜索这个函数,然后调用这个函数,返回一个表,然后把这个表赋值给模块名,然后就可以使用了。
int
luaopen_uv_c(lua_State *L) { // local tbl = require "tbl.c"
    // 1. 创建一张新的表,并预分配足够保存下数组 l 内容的空间
	luaL_newlibtable(L, l);// 1     创建一个新的表
    lua_pushinteger(L, 0);// 2       压入一个整数到栈顶
	luaL_setfuncs(L, l, 1);// 上值    把表和函数表关联起来,把栈顶的函数表和栈顶的表关联起来,然后返回Lua一个表
    // luaL_newlib(L, l);
	return 1;
}

我们在这里可以看到上值的作用:

Lua 复制代码
package.cpath = "luaclib/?.so"
local so = require "uv.c"

so.echo("hello world1")
so.echo("hello world2")
so.echo("hello world3")
so.echo("hello world4")
so.echo("hello world5")
so.echo("hello world6")
so.echo("hello world7")
so.echo("hello world8")
so.echo("hello world9")

我们在这里明明没有传入任何数字,并且没有看到任何数组,只是在3中我们压入了一个上值,然后根据闭包,我们的数值会一直增长,并且在函数内这个上值是共享的。

Lua 复制代码
[n=1]---hello world1
[n=2]---hello world2
[n=3]---hello world3
[n=4]---hello world4
[n=5]---hello world5
...

本篇主要讲解了Lua和C之间的接口设计,加入了Lua是动态语言的作用。0voice · GitHub

相关推荐
SoraLuna3 分钟前
「Mac玩转仓颉内测版25」基础篇5 - 布尔类型详解
开发语言·算法·macos·cangjie
Du_XiaoNan21 分钟前
Flowable第三篇、Flowable之任务分配(任务分配、流程变量、候选人和候选人组)
java·开发语言
Clang's Blog23 分钟前
23种设计模式详解(以Java为例)
java·开发语言·设计模式
main_Java28 分钟前
Android解压zip文件到指定目录
android·java·开发语言
GISer_Jing39 分钟前
Tomcat和Nginx原理说明
服务器·开发语言·javascript
z5418381 小时前
装饰器---python
开发语言·python
时光の尘1 小时前
C语言菜鸟入门·关键字·void的用法
c语言·开发语言·c++·算法·c#·c·关键字
Eric.Lee20211 小时前
图像上显示中文文本 - python 实现
开发语言·python
小林熬夜学编程1 小时前
【Linux系统编程】第四十九弹---日志系统构建指南:从基础结构到时间处理与Log类实现
linux·运维·服务器·c语言·开发语言·c++
恋猫de小郭1 小时前
Kotlin Multiplatform 未来将采用基于 JetBrains Fleet 定制的独立 IDE
开发语言·ide·kotlin