美文网首页我爱编程
C和Lua之间的相互调用

C和Lua之间的相互调用

作者: 李嘉的博客 | 来源:发表于2018-01-14 20:53 被阅读46次

    前面的话

    第一次接触Lua是因为Unity游戏中需要热更,但是一直没搞懂Lua是怎么嵌入到别的语言中执行的,如何互相调用的。这次打算好好了解一下C跟lua是如何交互的

    那么如何使用Lua语言?

    lua是c语言编写的,而且开源。可以在https://www.lua.org官网上下载Lua的源码,然后尝试编译它!是不是跟我一样好激动,一直用集成环境,写上层语言,今天居然要碰编译了!!~ 可怎么编译呢?

    让我们召唤出编译神器:gcc!【GNU编译器套件(GNU Compiler Collection)包括C、C++、Objective-C、Fortran、Java、Ada和Go语言的前端,也包括了这些语言的库(如libstdc++、libgcj等等)。】

    在Mac上安装GCC

    如果你安装了Homebrew的话,只要一行就可以了。

    brew install gcc
    

    装完后用

    brew info gcc
    或者
    gcc -v
    

    看一下是不是成功了


    编译Lua

    当你安装好了编译器后,编译lua就变得非常简单了



    Lua官网的文档里有说编译方式, 但MakeFile里默认的是编译成静态链接库,被这个坑了,后面再说

    建议安装在/opt目录下

    sudo su
    cd /opt
    curl -R -O http://www.lua.org/ftp/lua-5.3.4.tar.gz
    tar zxf lua-5.3.4.tar.gz
    cd lua-5.3.4
    make macosx test
    make macosx install
    

    安装好后用lua -v查看下如果有信息, 恭喜你,Lua编译好了!~

    下面正式开干了~

    写一个C调用Lua的Demo编译运行

    add.c内容

    //你需要include这几个lua头文件
    #include        <stdio.h>
    #include        "lua.h"
    #include        "lualib.h"
    #include        "lauxlib.h"
    
    lua_State* L;
    int
    luaadd(int x, int y)
    {
        int sum;
        /*函数名*/
        lua_getglobal(L,"add");
        /*参数入栈*/
        lua_pushnumber(L, x);
        /*参数入栈*/
        lua_pushnumber(L, y);
        /*开始调用函数,有2个参数,1个返回值*/
        lua_call(L, 2, 1);
        /*取出返回值*/
        sum = (int)lua_tonumber(L, -1);
        /*清除返回值的栈*/
        lua_pop(L,1);
        return sum;
    }
    
    int
    main(int argc, char *argv[])
    {
        int sum;
        L = luaL_newstate();  /* 创建lua状态机 */
        luaL_openlibs(L);   /* 打开Lua状态机中所有Lua标准库 */
        /*加载lua脚本*/
        luaL_dofile(L, "add.lua");
        /*调用C函数,这个里面会调用lua函数*/
        sum = luaadd(99, 10);
        printf("The sum is %d \n",sum);
        /*清除Lua*/
        lua_close(L);
        return 0;
    }
    
    

    add.lua放到与C同级的目录下,里面写一个简单的函数,让C调用

    function add(x,y)
           return x + y
    end 
    

    好了,终于到了用GCC编译的阶段了,直接gcc add.c一下看看行不行。



    果然报错了!

    这是因为没有把add.c里面的函数链接到我们前面编译出来的lua库里导致的。怎么让他指定链接哪个库呢?看GCC的文档得知-l参数可以指定要链接的库

    -l参数和-L参数
    -l参数就是用来指定程序要链接的库,-l参数紧接着就是库名,那么库名跟真正的库文
    件名有什么关系呢?
    就拿数学库来说,他的库名是m,他的库文件名是libm.so,很容易看出,把库文件名的
    头lib和尾.so去掉就是库名了

    那我们再试一下,gcc add.c -llua,这次编译出来了: a.out



    执行成功!

    如何让Lua调用C?

    Lua调用C,我了解到的有3种方式

    1.通过在C中注册函数给lua调用
    2.封装成c动态链接库,在lua中require
    3.在LuaJIT里面可以使用ffi高性能的调用C(但是IOS上不支持LuaJIT。。)

    1.在C中注册函数给Lua
    lua提供了lua_register函数注册C函数给lua端调用
    hello.c

    #include        <stdio.h>
    #include        <string.h>
    #include        "lua.h"
    #include        "lualib.h"
    #include        "lauxlib.h"
    
    
    static int l_SayHello(lua_State *L)
    {
        const char *d = luaL_checkstring(L, 1);//获取参数,字符串类型
        int len = strlen(d);
        char str[100] = "hello ";
        strcat(str, d);
        lua_pushstring(L, str);  /* 返回给lua的值压栈 */
        return 1;
    }
    
    int
    main(int argc, char *argv[])
    {
        lua_State *L = luaL_newstate();  /* 创建lua状态机 */
        luaL_openlibs(L);   /* 打开Lua状态机中所有Lua标准库 */
        lua_register(L, "SayHello", l_SayHello);//注册C函数到lua
    
        const char* testfunc = "print(SayHello('lijia'))";//lua中调用c函数
        if(luaL_dostring(L, testfunc))    // 执行Lua命令。
            printf("Failed to invoke.\n");
    
        /*清除Lua*/
        lua_close(L);
        return 0;
    }
    

    gcc -o hello hello.c -llua编译执行


    2.调用C动态链接库
    创建一个mylib.c的文件,然后我们把它编译成动态链接库

    #include <stdio.h>
    #include <math.h>
    #include <stdarg.h>
    #include <stdlib.h>
    #include <lua.h>
    #include <lauxlib.h>
    #include <lualib.h>
    
    /* 所有注册给Lua的C函数具有
     * "typedef int (*lua_CFunction) (lua_State *L);"的原型。
     */
    static int l_sin(lua_State *L)
    {   
        // 如果给定虚拟栈中索引处的元素可以转换为数字,则返回转换后的数字,否则报错。
        double d = luaL_checknumber(L, 1);
        lua_pushnumber(L, sin(d));  /* push result */
    
        /* 这里可以看出,C可以返回给Lua多个结果,
         * 通过多次调用lua_push*(),之后return返回结果的数量。
         */
        return 1;  /* number of results */
    }
    
    /* 需要一个"luaL_Reg"类型的结构体,其中每一个元素对应一个提供给Lua的函数。
     * 每一个元素中包含此函数在Lua中的名字,以及该函数在C库中的函数指针。
     * 最后一个元素为“哨兵元素”(两个"NULL"),用于告诉Lua没有其他的函数需要注册。
     */
    static const struct luaL_Reg mylib[] = {
        {"mysin", l_sin},
        {NULL, NULL}
    };
    
    /* 此函数为C库中的“特殊函数”。
     * 通过调用它注册所有C库中的函数,并将它们存储在适当的位置。
     * 此函数的命名规则应遵循:
     * 1、使用"luaopen_"作为前缀。
     * 2、前缀之后的名字将作为"require"的参数。
     */
    extern int luaopen_mylib(lua_State* L)
    {
        /* void luaL_newlib (lua_State *L, const luaL_Reg l[]);
         * 创建一个新的"table",并将"l"中所列出的函数注册为"table"的域。
         */ 
        luaL_newlib(L, mylib);
    
        return 1;
    }
    

    使用gcc -o mylib.so -fPIC -shared mylib.c -llua -ldl编译成so
    然后创建一个lua文件,把我们编译出来的c库引入进来

    --[[ 这里"require"的参数对应C库中"luaopen_mylib()"中的"mylib"。
         C库就放在"a.lua"的同级目录,"require"可以找到。]]
    local mylib = require "mylib"
    
    -- 结果与上面的例子中相同,但是这里是通过调用C库中的函数实现。
    print(mylib.mysin(3.14 / 2))    --> 0.99999968293183
    

    执行a.lua文件,后报错,说Lua存在多个虚拟机!
    lua: multiple Lua VMs detected


    为什么呢?查了一些资料发现因为lua默认编译的是静态链接库,这样会导致链接多个VM冲突。
    那么我们自己再编译个lua解释器动态链接一下。
    mylua.c

    #include <stdio.h>
    #include "lua.h"
    #include "lualib.h"
    #include "lauxlib.h"
    
    int main() {
    
        lua_State *L = luaL_newstate();
        luaL_openlibs(L);
    if (luaL_loadfile(L, "a.lua") || lua_pcall(L, 0, 0, 0)) {
            printf("%s", lua_tostring(L, -1));
        }
    
    }
    

    gcc -o mylua mylua.c -llua -ldl -lm -Wall
    这样就能编译出mylua可执行文件
    在命令行./mylua执行,成功打印出0.99999968293183

    总结

    gcc命令,编译lua,编译C动态链接库这些之前都接触的比较少。所以也爬了不少坑,哈哈哈。接下来要好好研究下怎么在c中解析二进制协议给lua调用,在c中怎么封装好luatable

    参考资料:
    https://www.cnblogs.com/pied/archive/2012/10/26/2741601.html
    http://blog.csdn.net/vermilliontear/article/details/50947379
    http://blog.csdn.net/casularm/article/details/316149

    相关文章

      网友评论

        本文标题:C和Lua之间的相互调用

        本文链接:https://www.haomeiwen.com/subject/xvgkoxtx.html