请选择 进入手机版 | 继续访问电脑版

risc-v中文社区

 找回密码
 立即注册
查看: 1727|回复: 0

Lua源码分析 - 实战篇 - 编写Lua的扩展库(20)

[复制链接]

347

主题

564

帖子

2237

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
2237
发表于 2022-6-20 16:03:01 | 显示全部楼层 |阅读模式
目录

一、实战篇 - 注册方式实现函数扩展

二、实战篇 - 编写标准的扩展库

三、实战篇 - 动态库加载方式实现

四、实战篇 - 编译Lua的动态库liblua.so

Lua的扩展库编写有三种方式:

注册方式:在主体语言里面编写扩展函数,然后通过lua_register方式,注册到Lua全局注册表中
扩展库编写:按照Lua语言本身的扩展库设计方式进行编写
动态库加载方式:将扩展库编译成.so的动态库,Lua语言中通过require方式动态加载库文件
如果忘记了扩展库的实现,可以回顾一下下面两篇文章:

《Lua源码分析 - 扩展库篇 - 扩展库Require的实现(12)》

《Lua源码分析 - 扩展库篇 - 扩展库Open的实现(13)》

一、实战篇 - 注册方式实现函数扩展
注册方式,我们在主体语言中,自定义一个my_func函数,入参为lua_State *L,函数内容将一个数字加上100。

然后调用lua_register函数,将my_func函数注册到Lua栈的全局注册表中。

当Lua语言中,遇到这个注册的C语言全局函数的时候,就会执行该函数。

    #include <stdio.h>
    #include <stdlib.h>
    #include  <math.h>
    #include  <lua.h>
    #include  <lualib.h>
    #include <lauxlib.h>


    //自定义一个Lua扩展函数
    static int my_func(lua_State *L) {
        int d = luaL_checkinteger(L, 1);
        lua_pushinteger(L, d + 100);  /* push result */
        return 1;
    }

    int main() {

        printf("----------Hello Lua API------------\n");

        /* 初始化一个栈 */
        lua_State *L = luaL_newstate();
        luaL_openlibs(L);
        lua_pushstring(L, "Lua API");
        lua_pushstring(L, "Hello Lua");

        int size = lua_gettop(L);
        printf("栈个数:%i\n", size);

        /* 注册函数 */
        lua_register(L, "my_func", my_func);

        /* 文件加载方式 */
        luaL_loadfile(L, "test.lua"); //加载文件
        int size2 = lua_gettop(L);
        printf("加载文件后,栈个数:%i\n", size2);
        lua_pcall(L, 0, 0, 0); //执行文件


        /* 关闭栈 */
        lua_close(L);
        printf("----------Hello Lua API Close------------\n");

        return 0;
    }


//test.lua

print("luaL_loadfile:执行文件加载的Lua脚本!");
print("自定义函数调用:" .. my_func(100));
编译并执行,我们可以看到,在test.lua中调用my_func的执行结果,并打印出来

***6f:Togo zhuli$ gcc main.c -o main -l lua
***6f:Togo zhuli$ ./main
----------Hello Lua API------------
栈个数:2
加载文件后,栈个数:3
luaL_loadfile:执行文件加载的Lua脚本!
自定义函数调用:200
----------Hello Lua API Close------------
二、实战篇 - 编写标准的扩展库
编写一个独立的扩展库文件,需要包含三个维度内容:

扩展函数:自定义一个扩展函数,入参为lua_State对象,函数如果要获取外部入参参数,则从Lua的栈上获取。扩展函数默认返回1,则执行成功。
配置数组:主要定义函数名称和扩展库中函数的映射关系。函数名称即为Lua语言中可以回调的函数。
模块配置:定义一个以luaopen_*开头的函数,内部调用luaL_newlib函数实现模块的初始化。一般Lua中使用:模块名称.函数名称
我们定义了lmylib.c的文件,放入Lua的src/目录下。

    #define lmylib_c
    #define LUA_LIB

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    #include <stdarg.h>

    #include "lprefix.h"
    #include "lua.h"
    #include "lualib.h"
    #include "lauxlib.h"

    //入参lua_State *L
    //返回1 则函数执行成功
    //通过luaL_checkinteger获取入参
    static int mylib_number(lua_State *L) {
        int d = luaL_checkinteger(L, 1); //获取参数
        lua_pushinteger(L, d + 100);  /* push result */
        return 1;
    }

    //mylib_number函数名
    //调用方式:mylib.mylib_number(99)
    static const struct luaL_Reg mylib[] = {
        {"mylib_number", mylib_number},
        {NULL, NULL}
    };

    //mylib = 模块名称
    // luaL_newlib 初始化模块
    extern int luaopen_mylib(lua_State* L) {
        luaL_newlib(L, mylib);
        return 1;
    }
添加了模块后,我们需要修改Lua的其它两个文件:

lualib.h文件:添加LUA_MYLIBNAME和LUAMOD_API
linit.c文件:添加{LUA_MYLIBNAME, luaopen_mylib}
//lualib.h文件
#define LUA_MYLIBNAME        "mylib"
LUAMOD_API int (luaopen_mylib) (lua_State *L);

//linit.c文件
{LUA_MYLIBNAME, luaopen_mylib},

然后我们需要修改src/Makefile文件,主要用于编译的时候生成目标对象lmylib.o文件。

LIB_O=        lauxlib.o lbaselib.o lbitlib.o lcorolib.o ldblib.o liolib.o \
        lmathlib.o loslib.o lstrlib.o ltablib.o lutf8lib.o loadlib.o linit.o lmylib.o

lmylib.o: lmylib.c lprefix.h lua.h luaconf.h lauxlib.h lualib.h

重新安装Lua,执行unstall、clean、make、make install 命令

**:lua-5.3.5 zhuli$ sudo make uninstall
......

**:lua-5.3.5 zhuli$ sudo make macosx clean
....

**:lua-5.3.5 zhuli$ sudo make macosx
....
gcc -std=gnu99 -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX    -c -o linit.o linit.c
gcc -std=gnu99 -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX    -c -o lmylib.o lmylib.c

**:lua-5.3.5 zhuli$ sudo make install
cd src && mkdir -p /usr/local/bin /usr/local/include /usr/local/lib
/usr/local/man/man1 /usr/local/share/lua/5.3 /usr/local/lib/lua/5.3
cd src && install -p -m 0755 lua luac /usr/local/bin
cd src && install -p -m 0644 lua.h luaconf.h lualib.h lauxlib.h lua.hpp /usr/local/include
cd src && install -p -m 0644 liblua.a /usr/local/lib
cd doc && install -p -m 0644 lua.1 luac.1 /usr/local/man/man1
我们执行lua test.lua命令,则我们的扩展函数调用成功了

//test.lua
print("---------加载扩展函数------------");
print(mylib.mylib_number(999));

//直接调用Lua
**:Togo zhuli$ lua test.lua
---------加载扩展函数------------
1099

//宿主方式调用test.lua文件
**:Togo zhuli$ ./main
----------Hello Lua API------------
栈个数:2
加载文件后,栈个数:3
---------加载扩展函数------------
1099
----------Hello Lua API Close------------


三、实战篇 - 动态库加载方式实现
标准的扩展库,需要将扩展库直接编译进Lua源码里面,并且需要修改lualib.h和linit.c文件,对于集群线上大批量部署Lua语言,会造成巨大的困难。

Lua可以通过requre方式动态加载扩展库,所以我们可以将扩展库编译成.so的文件,就可以实现动态加载方式。

动态库和静态库的区别:

静态库:在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
动态库:在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
先看一下,我们编写好的动态库mylibi.c代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    #include <stdarg.h>

    #include <lua.h>
    #include <lualib.h>
    #include <lauxlib.h>

    int luaopen_mylibi(lua_State *L);

    static int add(lua_State *L) {
        int d = luaL_checkinteger(L, 1);
        lua_pushinteger(L, d + 100);  /* push result */
        return 1;
    }

    static const struct luaL_Reg mylibi[] = {
        {"add", add},
        {NULL, NULL}
    };

    extern int luaopen_mylibi(lua_State* L) {
        luaL_newlib(L, mylibi);
        return 1;
    }
我们需要将mylibi.c编译成mylibi.so动态库文件。但是,如果我们编译的扩展库.so文件,依赖的是lua的静态库,则动态加载的时候就会出现多个虚拟机的报错。静态编译(-l lua),mylibi.so里面会将lua虚拟机代码加载进去进行独立编译,Lua的解析器又加载了一遍,所以会导致多个虚拟机冲突。

***:Togo zhuli$ gcc mylibi.c -fPIC -shared -o mylibi.so -l lua
***:Togo zhuli$ lua test.lua
---------加载扩展函数------------
lua: multiple Lua VMs detected
stack traceback:
        [C]: in ?
        [C]: in function 'require'
        test.lua:3: in main chunk
        [C]: in ?
Linux和Mac系统下的so文件编译方式不太一样,需要注意。编译完成后,mylibi.so和Lua执行脚本同一个目录下

Linux系统命令:gcc mylibi.c -fPIC -shared -o mylibi.so
macosx系统命令:gcc -O2 -fPIC -Wall -bundle -undefined dynamic_lookup -o mylibi.so  mylibi.c
//命令
gcc -O2 -fPIC -Wall -bundle -undefined dynamic_lookup -o mylibi.so  mylibi.c

//执行
***:Togo zhuli$ lua test.lua
---------加载扩展函数------------
1124

//test.lua
print("---------加载扩展函数------------");
local mylibi = require("mylibi"); //动态加载mylibi
print(mylibi.add(1024)); //调用mylibi.add函数
在C语言中动态调用Lua语言,也同样成功:

ali-186590d6066f:Togo zhuli$ gcc main.c -o main -l lua
ali-186590d6066f:Togo zhuli$ ./main
----------Hello Lua API------------
栈个数:2
加载文件后,栈个数:3
---------加载扩展函数------------
1124
----------Hello Lua API Close------------


//main.c
    #include <stdio.h>
    #include <stdlib.h>
    #include  <math.h>
    #include  <lua.h>
    #include  <lualib.h>
    #include <lauxlib.h>

    int main() {

        printf("----------Hello Lua API------------\n");

        /* 初始化一个栈 */
        lua_State *L = luaL_newstate();
        luaL_openlibs(L);
        lua_pushstring(L, "Lua API");
        lua_pushstring(L, "Hello Lua");

        int size = lua_gettop(L);
        printf("栈个数:%i\n", size);


        /* 文件加载方式 */
        luaL_loadfile(L, "test.lua"); //加载文件
        int size2 = lua_gettop(L);
        printf("加载文件后,栈个数:%i\n", size2);
        lua_pcall(L, 0, 0, 0); //执行文件


        /* 关闭栈 */
        lua_close(L);
        printf("----------Hello Lua API Close------------\n");

        return 0;
    }
四、实战篇 - 编译Lua的动态库liblua.so
有些场景下,我们需要动态加载Lua的动态库。Lua语言本身编译的时候只生成静态库,要解决这个问题,我们需要将lua的动态库liblua.so(默认静态库liblua.a)也编译出来。

编译Lua的时候,需要修改一下Lua的Makefile文件(主Makefile和src/Makefile):

//修改src/Makefile文件,添加liblua.so
LUA_SO=liblua.so
ALL_T= $(LUA_A) $(LUA_T) $(LUAC_T) $(LUA_SO)
$(LUA_SO): $(CORE_O) $(LIB_O)
    $(CC) -o $@ -shared $?

//修改Makefile文件,TO_LIB结果对象加上liblua.so
TO_LIB= liblua.a liblua.so
重新安装Lua,执行unstall、clean、make、make install 命令

我们看到liblua.so被拷贝到了 /usr/local/lib文件夹下,然后我们在文件夹下找到liblua.so

**:lua-5.3.5 zhuli$ sudo make uninstall
......

**:lua-5.3.5 zhuli$ sudo make macosx clean
....

**:lua-5.3.5 zhuli$ sudo make macosx
....
gcc -std=gnu99 -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX    -c -o linit.o linit.c
gcc -std=gnu99 -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX    -c -o lmylib.o lmylib.c

**:lua-5.3.5 zhuli$ sudo make install
cd src && mkdir -p /usr/local/bin /usr/local/include /usr/local/lib
/usr/local/man/man1 /usr/local/share/lua/5.3 /usr/local/lib/lua/5.3
cd src && install -p -m 0755 lua luac /usr/local/bin
cd src && install -p -m 0644 lua.h luaconf.h lualib.h lauxlib.h lua.hpp /usr/local/include
cd src && install -p -m 0644 liblua.a liblua.so /usr/local/lib
cd doc && install -p -m 0644 lua.1 luac.1 /usr/local/man/man1

————————————————
版权声明:本文为CSDN博主「老码农zhuli」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/initphp/article/details/105438459

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



Archiver|手机版|小黑屋|risc-v中文社区

GMT+8, 2024-3-29 19:52 , Processed in 0.015464 second(s), 17 queries .

risc-v中文社区论坛 官方网站

Copyright © 2018-2021, risc-v open source

快速回复 返回顶部 返回列表